All pastes #1269000 Raw Edit

Miscellany

public text v1 · immutable
#1269000 ·published 2008-11-27 22:43 UTC
rendered paste body
# -----------------------------------------------------------------------------
#    Copyright (c) 2007, 2008 by Severn Clay
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#
# -----------------------------------------------------------------------------
import Blender
from Blender import Mesh
from Blender.Mathutils import CrossVecs, Vector, Matrix, AngleBetweenVecs, MidpointVecs

try:
    from scipy import weave
    WEAVE = True
except:
    print('Weave library not found - you will experience somewhat slower rendering times, sorry.')
    WEAVE = False    
    
try:
    import ming
except:
    print('Ming library not found - you will not be able to render animations')

from copy import deepcopy, copy
from bisect import insort
from math import tan,atan,pi,degrees,sqrt,radians
from time import clock
from itertools import repeat
import cPickle
import sys, os, gc, types

try:
    import cairo
except:
    print "You need to install  Cairo",
    print "or set your PYTHONPATH correctly."
    print "try: export PYTHONPATH=/usr/lib/python2.4/site-packages",
    sys.exit(1)
    
try:
    from Polygon import *
except:
    print "Make sure Polygon is installed and properly compiled"
    print "and linked for your installation of Python"
    print "http://download.dezentral.de/soft/Python/Polygon/"
        
#---GLOBAL VARIABLES---#000000#FFFFFF-------------------------------------------
#Run-time settings###################
DEBUG = False
PROGRESS = True
PROFILE = True

#Global variables####################
CLASSIFY_CALLS = 0
SPLIT_CALLS = 0
MAX_DEPTH = 0
PROCESSED_FACES = 0
TOTAL_FACES = 0
CLIP_COUNTER = 0

EPSILON3D = .0001
EPSILON2D = .0001
setTolerance(EPSILON2D)                                                     #this controls the tolerance of the Polygon package
RECURSION_LIMIT = 100                                                       #this is set to prevent the BSP algorithm from over-recursing

#Spacial Classification##############
NOT_SET = None
IN_FRONT = 1000
ON = 1001
BEHIND = 1002
INTERSECTING = 1003

#Face Types##########################
GEOMETRY_TYPE = 1004
CUTTING_TYPE = 1005
PARTITION_TYPE = 1006
FRUSTUM_TYPE = 1007
VIGNETTE_TYPE = 1008

FRONT_FACING = 1009
BACK_FACING = 1010

#User Variables######################
#These will be user-settable#########
CULL_BACKFACES = False
USE_ALPHA = True
RENDER_ANIMATION = False
AUTO_CREASE = False
LOAD_PREVIOUS_SETTINGS = True

#Installation settings###############
##!!! Homedir should eventually be set by the installation program !!!
HOMEDIR = "/home/agiofws/zips/panto/"    
LIB_VERSION = "0.5"

#---UTILITY CLASSES AND FUNCTIONS---#000000#FFFFFF--------------------------------
def debug(*args):
    if DEBUG: print args
    
def progress(*args):
    if PROGRESS: print args
    
def distance(start,end):
    """
    returns the signed distance between two vectors(x,y,z)
    by taking the sqrt of the dot product of the difference of the two vectors with itself
    """
    difference = (start - end)
    result = sqrt(difference * difference)
    return result

def intersect(firstList,secondList):
    """
    returns the intersection between two lists or tuples
    """
    return [item for item in firstList if item in secondList]

class Profiler:
    """
    A utility class intended for timing code blocks
    """
    def __init__(self,name):
        self.total = 0
        self.begin = 0
        self.name = name
        
    def start(self):
        if not self.begin and PROFILE:
            self.begin = clock()
    
    def end(self):
        if self.begin and PROFILE:
            self.total = self.total + (clock() - self.begin)
            self.begin = 0
        
        return (self.name + " took %.3f minutes" % (self.total/60.00))
            
#---3D GEOMETRY FUNCTIONS---#000000#FFFFFF-----------------------------
def doCrease(edge, angle):
    """
    Marks the edge as creased if the incident faces are less than the given angle
    """
    try:
        if (AngleBetweenVecs(edge.faces[0].normal, edge.faces[1].normal) > angle) and (not edge.SILHOUETTE):
            edge.SHARP = True
        else:
            edge.SHARP = False
    except:
        edge.SHARP = False
        
def doCornerDetection(edge, angle):
    """
    Find edge neighbours for the edge, and classify them as hard or soft
    If SHARP and not SHARP: hard connection
    If SHARP and SHARP: soft if no other SHARP, else hard
    IF not SHARP and not SHARP: "
    All connections with edges of a different type are hard.
    "angle" is the angle at which an edge is automatically creased
    """
    if edge.SILHOUETTE:
        for vert in edge.verts:
            if otherEdge in vert.edges:
                if otherEdge != edge:
                    print("This isn't finished yet")
                    
        
def computeNormal(face):
    """
    Returns a normalized vector of a face's normal
    """
    vec1 = Vector(face.verts[1].loc.x,face.verts[1].loc.y,face.verts[1].loc.z)-Vector(face.verts[0].loc.x,face.verts[0].loc.y,face.verts[0].loc.z)
    vec2 = Vector(face.verts[2].loc.x,face.verts[2].loc.y,face.verts[2].loc.z)-Vector(face.verts[0].loc.x,face.verts[0].loc.y,face.verts[0].loc.z)
    normal = CrossVecs(vec1, vec2)
    #normal = normal.normalize()
    normal = Vector(normal.x, normal.y, normal.z, 0)
    
    face.normal = normal
    
    face.plane = Plane(face.normal,face.verts[0].loc)
        
def clip2Window(vert, drawing):
    """
    Returns window coordinates for a vertex
    """
    WIDTH = drawing.WIDTH
    HEIGHT = drawing.HEIGHT
    
    if not vert.loc2d:
        #convert to homogenous (-1,1) components
        vert.loc2d = Vector(0,0)
        vert.loc2d.x = vert.loc.x / vert.loc.w
        vert.loc2d.y = vert.loc.y / vert.loc.w
        
        #convert to window coordinates
        vert.loc2d.x = ((WIDTH/2) * vert.loc2d.x) + (WIDTH / 2)
        vert.loc2d.y = ((HEIGHT/2) * -vert.loc2d.y) + (HEIGHT / 2)
                
def classifyPointPlane(vec, plane):
    """
    Classify the given point with respect to a plane:
    Returns 
    IN_FRONT if in front of plane,
    ON if on the plane ,
    BEHIND if behind the plane
    
    modeled after 
    http://mathworld.wolfram.com/Point-PlaneDistance.html
    """
    global CLASSIFY_CALLS
    
    CLASSIFY_CALLS += 1
    
    w = plane.point - vec
    dist = (plane.normal * w) / plane.normal.length
    
    if abs(dist) < EPSILON3D:
        return ON
    
    elif dist > EPSILON3D:
        return IN_FRONT
    
    else:
        return BEHIND
    
def classifyFaceFace(face, otherFace, classifyDict):
    """
    classifies the face as IN_FRONT, BEHIND or ON.
    sets classifyDict with classifications for all the verts.
    """
    intersectionVerts = []
                   
    #Make a dictionary with the classification of each vert in face
    [classifyDict.__setitem__(vert, classifyPointPlane(vert.loc, otherFace.plane)) for vert in face.verts]
    
    front = False
    back = False

    for vert in classifyDict.keys():
        if front and back: break #we're already straddling the plane
        
        if classifyDict[vert]==IN_FRONT:
            front=True
            
        elif classifyDict[vert]==BEHIND:
            back=True
            
        elif classifyDict[vert]==ON:
            pass
            
    if not front and not back:
        return ON
    
    elif front and back:
        return INTERSECTING
        
    elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
        return IN_FRONT
    
    elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
        return BEHIND
    
def splitFaceFace(face, otherFace, classifyDict):
    """
    First attempts to classify the first face in front or behind the other.
    NOTE: This takes into account whether the splitting face is front-facing or back-facing!
    Returns IN_FRONT or BEHIND if not intersecting.
    
    If intersecting, returns a tuple of(back polygon, front polygon).
    Assumes that both polys are convex. Maintains proper vertex order! 
    """
    ###Attempt to classify the face according to the splitting face    
    front=False
    back=False
    
    for vert in face.verts:
        if front and back:
            break                                                           #we're already straddling the plane
        
        elif classifyDict[vert]==IN_FRONT:
            front=True
            
        elif classifyDict[vert]==BEHIND:
            back=True
            
        elif classifyDict[vert]==ON:
            pass
            
    if not front and not back:
        return ON                                                           #this would occur only if the faces are parallel and coincident
    
    elif front and back:
        pass
        
    elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
        return IN_FRONT
    
    elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
        return BEHIND
    
    ###We know that we have a split resulting in two new faces
    frontFace = face                                                        #rather than making TWO copies, frontFace points to the original face
    backFace = copy(face)
    
    intersectionVerts = list()
    
    edgeDestroySet = set()
    
    frontFaceVertsRemoveList = list()
    backFaceVertsRemoveList = list()
    frontFaceEdgeRemoveList = list()
    backFaceEdgeRemoveList = list()
            
    for myEdge in copy(face.edges):
        vert = myEdge.verts[0]
        nextVert = myEdge.verts[1]
        
        if classifyDict[vert] == ON:                                        #this is an intersection pt
            if (not vert in intersectionVerts):
                intersectionVerts.append(vert)
            
        elif classifyDict[nextVert] == ON:                                  #this is an intersection pt
            if (not nextVert in intersectionVerts): 
                intersectionVerts.append(nextVert)
            
        elif (classifyDict[vert] == IN_FRONT and classifyDict[nextVert] == BEHIND) or \
            (classifyDict[vert] == BEHIND and classifyDict[nextVert] == IN_FRONT):
            
            splitEdges = splitEdgeFace(myEdge, otherFace, classifyDict)
            
            if splitEdges:
                try:                                                             #our edge is split into two edges
                    intersectionPt = intersect(splitEdges[0].verts, splitEdges[1].verts)[0]
                    edgeDestroySet.add(myEdge)
                    
                except:                                                          #this edge has already been split, or is being split on one of its endpoints
                    intersectionPt = splitEdges[2]
                    if splitEdges[0]:
                        backFaceEdgeRemoveList.append(splitEdges[0])
                    elif splitEdges[1]: 
                        frontFaceEdgeRemoveList.append(splitEdges[1])
                        
            else:
                raise "No split where there should have been one!"
                
            classifyDict[intersectionPt] = ON
            
            intersectionVerts.append(intersectionPt)
    
    [myEdge.destroy() for myEdge in edgeDestroySet]
        
    #Check the classification of each vert, and remove it from the face on the opposite side
    for vert in face.verts:
        if not vert in intersectionVerts: #the intersection verts haven't been classified, and are in both faces anyway
            if classifyDict[vert] == IN_FRONT:
                backFaceVertsRemoveList.append(vert)
                        
            elif classifyDict[vert] == BEHIND:
                frontFaceVertsRemoveList.append(vert)
            
    #Check the classification of each edge, and remove it from the face on the opposite side
    for edge in face.edges:
        if edge.verts[0] not in intersectionVerts and classifyDict[edge.verts[0]] == IN_FRONT:
            if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == IN_FRONT or classifyDict[edge.verts[1]] == ON:
                if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge)
            
        elif edge.verts[0] not in intersectionVerts and classifyDict[edge.verts[0]] == BEHIND:
            if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == BEHIND or classifyDict[edge.verts[1]] == ON:
                if not edge in frontFaceEdgeRemoveList: frontFaceEdgeRemoveList.append(edge)
                
        elif edge.verts[0] in intersectionVerts or classifyDict[edge.verts[0]] == ON:
            if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == IN_FRONT:
                if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge)
                
            elif edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == BEHIND:
                if not edge in frontFaceEdgeRemoveList: frontFaceEdgeRemoveList.append(edge)
                                
    frontFace.remove(verts = frontFaceVertsRemoveList, edges = frontFaceEdgeRemoveList)
    backFace.remove(verts = backFaceVertsRemoveList, edges = backFaceEdgeRemoveList)
    
    #Make an intersection edge
    if len(intersectionVerts) < 2:
        if DEBUG: face.materialName = 'DEBUG'
        return (face, face) # for now
        #raise "SplitFaceFace: Too few intersection points!"
    
    elif len(intersectionVerts) > 2:
        if DEBUG: face.materialName = 'DEBUG'
        return (face, face) # for now

    intersectionEdge = Edge(intersectionVerts,face.mesh)
    
    if otherFace.facing == BACK_FACING:
        intersectionEdge.CUTISVISIBLE = True #If the cut edge is visible to the camera, mark it so a cutaway would be visible
    
    #Figure out where to insert the edge
    for myFace in frontFace, backFace:
        for edge in copy(myFace.edges):
            if len(intersect(edge.verts, intersectionVerts)) == 1:
                myFace.insert((myFace.edges.index(edge) + 1), edges = [intersectionEdge])
                break
    
    if len(frontFace.verts) != len(frontFace.edges):
        if DEBUG: face.materialName = 'DEBUG'
        return (frontFace, face) # for now
        #raise IndexError, "The number of verts(%i) does not match the number of edges(%i)" % (len(frontFace.verts), len(frontFace.edges))
    
    if len(backFace.verts) != len(backFace.edges):
        if DEBUG: face.materialName = 'DEBUG'
        return (face, backFace) # for now
        #raise IndexError, "The number of verts(%i) does not match the number of edges(%i)" % (len(backFace.verts), len(backFace.edges))
    
    frontFace.bbox = BoundingBox(frontFace.verts)
    backFace.bbox = BoundingBox(backFace.verts)
    
    global SPLIT_CALLS
    SPLIT_CALLS += 1
    
    if otherFace.facing == FRONT_FACING:
        myFrontFace = frontFace
        myBackFace = backFace
        
    elif otherFace.facing == BACK_FACING:
        myFrontFace = backFace
        myBackFace = frontFace
        
    return (myFrontFace, myBackFace) 

def splitCurveFace(curve, otherFace):
    """
    If intersecting, returns a tuple of (back curveList, on curveList, front curveList).
    """
    classifyDict = dict()
    [classifyDict.__setitem__(vert, classifyPointPlane(vert.loc, otherFace.plane)) for vert in curve.verts]

    front=False
    back=False

    for vert in curve.verts:
        if front and back:
            break #we're already straddling the plane
        
        elif classifyDict[vert]==IN_FRONT:
            front=True
            
        elif classifyDict[vert]==BEHIND:
            back=True
            
        elif classifyDict[vert]==ON:
            pass
            
    if not front and not back:
        return ON
    
    elif front and back:
        pass
        
    elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
        return IN_FRONT
    
    elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
        return BEHIND
    
    #we have a split, so we need to discover where the actual intersection(s) are/is
    edgeDict = dict()
    
    frontCurveList = []
    backCurveList = []
    onCurveList = []
    
    #set the flag on the first vert
    flag = classifyDict[curve.verts[0]]
    mySegment = Curve(curve.mesh, [curve.verts[0]], curve.materialName)
    
    #check each vert against the flag - if there is no change, append the vert and edge to mySegment.  
    #If there is a change, append a copy of mySegment to the proper list and make a new mySegment
    for (lastVert, vert, edge) in zip(curve.verts[:-1], curve.verts[1:], curve.edges):
            
        if flag == IN_FRONT:
            if classifyDict[vert] == BEHIND:
                if classifyDict[lastVert] == IN_FRONT:
                    splitTuple = splitEdgeFace(edge, otherFace, classifyDict)
                    if splitTuple:
                        try:                                                    #we have a normal split, resulting in two distinct edges
                            (myFrontEdge, myBackEdge) = splitTuple
                            myIntersectionPt = intersect(myFrontEdge.verts, myBackEdge.verts)
                            mySegment.append(verts = myIntersectionPt, edges = [myFrontEdge])
                            frontCurveList.append(copy(mySegment))
                            mySegment = Curve(curve.mesh, [myIntersectionPt[0], vert], curve.materialName)
                            mySegment.append(edges = [myBackEdge])
                        
                        except:                                                 #we split on a vert, resulting in only one edge
                            if splitTuple[0]:                                   #our split results in only a front edge
                                myIntersectionPt = splitTuple[2]
                                mySegment.append(verts = myIntersectionPt, edges = splitTuple[0])
                                frontCurveList.append(copy(mySegment))
                                mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
                                
                            else:                                               #our split results in only a back edge
                                myIntersectionPt = splitTuple[2]
                                mySegment.append(verts = myIntersectionPt, edges = splitTuple[1])
                                backCurveList.append(copy(mySegment))
                                mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
                                
                    else:
                        mySegment.append(verts = [vert], edges = [edge])
                
                elif classifyDict[lastVert] == ON:                          ###isn't this belt + suspenders?
                    frontCurveList.append(copy(mySegment))
                    mySegment = Curve(curve.mesh, [], curve.materialName)
                    mySegment.append(verts = [vert], edges = [edge])
                
                flag = BEHIND
            
            elif classifyDict[vert] == IN_FRONT or classifyDict[vert] == ON:
                mySegment.append(verts = [vert], edges = [edge])                                
    
        elif flag == BEHIND:
            if classifyDict[vert] == IN_FRONT:
                if classifyDict[lastVert] == BEHIND:
                    splitTuple = splitEdgeFace(edge, otherFace, classifyDict)
                    
                    if splitTuple:
                        try:                                                    #we have a normal split, resulting in two distinct edges
                            (myFrontEdge, myBackEdge) = splitTuple
                            myIntersectionPt = intersect(myFrontEdge.verts, myBackEdge.verts)
                            mySegment.append(verts = myIntersectionPt, edges = [myBackEdge])
                            frontCurveList.append(copy(mySegment))
                            mySegment = Curve(curve.mesh, [myIntersectionPt[0], vert], curve.materialName)
                            mySegment.append(edges = [myFrontEdge])
                        
                        except:                                                 #we split on a vert, resulting in only one edge
                            myIntersectionPt = splitTuple[2]
                            if splitTuple[0]:                                   #our split results in only a front edge
                                mySegment.append(verts = myIntersectionPt, edges = splitTuple[0])
                                frontCurveList.append(copy(mySegment))
                                mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
                                
                            elif splitTuple[0]:                                 #our split results in only a back edge
                                mySegment.append(verts = myIntersectionPt, edges = splitTuple[1])
                                backCurveList.append(copy(mySegment))
                                mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)                        
                    else:
                        mySegment.append(verts = [vert], edges = [edge])
                            
                elif classifyDict[lastVert] == ON:                          ###isn't this belt + suspenders?
                    frontCurveList.append(copy(mySegment))
                    mySegment = Curve(curve.mesh, [], curve.materialName)
                    mySegment.append(verts = [vert], edges = [edge])
                                        
                flag = IN_FRONT
                            
            elif classifyDict[vert] == BEHIND or classifyDict[vert] == ON:
                mySegment.append(verts = [vert], edges = [edge])
                
        elif flag == ON:
            if classifyDict[vert] == IN_FRONT:
                mySegment.append(verts = [vert], edges = [edge])
                flag = IN_FRONT
                
            elif classifyDict[vert] == BEHIND:
                mySegment.append(verts = [vert], edges = [edge])
                flag = BEHIND
                
            else:
                mySegment.append(verts = [vert], edges = [edge])
                
    if flag == IN_FRONT:
        frontCurveList.append(copy(mySegment))
        
    elif flag == BEHIND:
        backCurveList.append(copy(mySegment))
        
    elif flag == ON:
        onCurveList.append(copy(mySegment))
    
    if otherFace.facing == FRONT_FACING:
        return (backCurveList, onCurveList, frontCurveList)
    
    else:
        return (frontCurveList, onCurveList, backCurveList)
        
    
def segmentIntersection(edge, plane):
    """
    returns the intersection point with the plane on the edge
    if one of the verts is on the plane, or there is no intersection,
    it returns False.
    based on 
    http://geometryalgorithms.com/Archive/algorithm_0104/algorithm_0104B.htm#Line-Plane%20Intersection
    """
    u = edge.verts[1].loc - edge.verts[0].loc
    w = edge.verts[0].loc - plane.point
    
    D = plane.normal*u
    N = -plane.normal*w
    
    if abs(D) < EPSILON3D:
        return False #edge is parallel to the plane
    
    sI=N / D
    if sI < 0 or sI > 1:
        return False #intersection point not on edge
    
    intersectionPt=Vert(edge.scene, loc = edge.verts[0].loc + (sI * u))
    
    return intersectionPt
    
def splitEdgeFace(edge, face, classifyDict):
    """
    Splits an edge (or segment) by a face.
    classifyDict is a dict with the classifications of the verts according to the splitting face.
    
    If there is a normal intersection, returns a tuple of (frontEdge, backEdge)
    If the intersection is ON one of the points, returns a tuple (frontEdge, intersectionPt) or (intersectionPt, backEdge)
    if there is no intersection, returns (None, None)
    """
    intersectionPt = segmentIntersection(edge, face.plane)
    
    if not intersectionPt: return False                              #no intersection or the edge is parallel to the face
    
    if intersectionPt == edge.verts[0]:                                     #the intersectionPt is one of our endpoints
        classifyDict[edge.verts[0]] = ON
        if classifyDict[edge.verts[1]] == IN_FRONT:
            return (edge, None, intersectionPt)
        
        else:
            return (None, edge, intersectionPt)
        
    elif intersectionPt == edge.verts[1]:
        classifyDict[edge.verts[1]] = ON
        if classifyDict[edge.verts[0]] == IN_FRONT:
            return (edge, None, intersectionPt)
        
        else:
            return (None, edge, intersectionPt)
    
    #We must have a proper intersection, with two resulting segments:
    if classifyPointPlane(edge.verts[0].loc, face.plane) == IN_FRONT:       #the first vert is in front, so the first segment is in front
        frontVert = edge.verts[0]
        backVert = edge.verts[1]
        
    else:
        frontVert = edge.verts[1]
        backVert = edge.verts[0]
    
    frontEdge = copy(edge) 
    frontEdge.verts = [frontVert, intersectionPt]
    
    backEdge = copy(edge)
    backEdge.verts = [intersectionPt,backVert]
                    
    intersectionPt.edges = set([frontEdge, backEdge])
    
    #figure out where to insert the intersection point in adjoining faces
    for myFace in edge.faces:
        firstVertIndex =  myFace.verts.index(edge.verts[0])
        secondVertIndex = myFace.verts.index(edge.verts[1])
        
        if abs(firstVertIndex - secondVertIndex) > 1:
            myFace.append(verts = [intersectionPt])
            
        else:
            myFace.insert(max(firstVertIndex,secondVertIndex),verts = [intersectionPt])
    
    return (frontEdge,backEdge)
                
#---3D GEOMETRY CLASSES---#000000#FFFFFF-----------------------------
class BoundingBox:
    def __init__(self,vertList):
        """
        computes a bounding sphere for the object
        """
        self.xMin = self.xMax = self.yMin = self.yMax = self.zMin = self.zMax = NOT_SET
        
        try: #assume we have a 3d object with Vec3 locations
            for vert in vertList:
                if self.zMin>vert.loc.z or self.zMin is NOT_SET:self.zMin=vert.loc.z
                if self.zMax<vert.loc.z or self.zMax is NOT_SET:self.zMax=vert.loc.z            
                if self.xMin>vert.loc.x or self.xMin is NOT_SET:self.xMin=vert.loc.x
                if self.xMax<vert.loc.x or self.xMax is NOT_SET:self.xMax=vert.loc.x
                if self.yMin>vert.loc.y or self.yMin is NOT_SET:self.yMin=vert.loc.y
                if self.yMax<vert.loc.y or self.yMax is NOT_SET:self.yMax=vert.loc.y
                
            #Joggle the bounds to avoid flat bboxes
            if abs(self.xMin - self.xMax) < EPSILON3D: 
                self.xMin -= EPSILON3D
                self.xMax += EPSILON3D
                
            if abs(self.yMin - self.yMax) < EPSILON3D: 
                self.yMin -= EPSILON3D
                self.yMax += EPSILON3D
                
            if abs(self.zMin - self.zMax) < EPSILON3D: 
                self.zMin -= EPSILON3D
                self.zMax += EPSILON3D
                
        except: #No? Must be 2d then
            for vert in vertList:
                if self.xMin>vert.loc2d.x or self.xMin is NOT_SET:self.xMin=vert.loc2d.x
                if self.xMax<vert.loc2d.x or self.xMax is NOT_SET:self.xMax=vert.loc2d.x
                if self.yMin>vert.loc2d.y or self.yMin is NOT_SET:self.yMin=vert.loc2d.y
                if self.yMax<vert.loc2d.y or self.yMax is NOT_SET:self.yMax=vert.loc2d.y
                
            #Joggle the bounds to avoid flat bboxes
            if abs(self.xMin - self.xMax) < EPSILON2D: 
                self.xMin -= EPSILON2D
                self.xMax += EPSILON2D
                
            if abs(self.yMin - self.yMax) < EPSILON2D: 
                self.yMin -= EPSILON2D
                self.yMax += EPSILON2D

    def doesIntersect(self,otherBbox):
        if (((self.xMin<otherBbox.xMin<self.xMax) or (self.xMin<otherBbox.xMax<self.xMax) or (otherBbox.xMin<self.xMin<otherBbox.xMax) or (otherBbox.xMin<self.xMax<otherBbox.xMax))
            and ((self.yMin<otherBbox.yMin<self.yMax) or (self.yMin<otherBbox.yMax<self.yMax) or (otherBbox.yMin<self.yMin<otherBbox.yMax) or (otherBbox.yMin<self.yMax<otherBbox.yMax))
            and ((self.zMin<otherBbox.zMin<self.zMax) or (self.zMin<otherBbox.zMax<self.zMax) or (otherBbox.zMin<self.zMin<otherBbox.zMax) or (otherBbox.zMin<self.zMax<otherBbox.zMax))):
            return True
        
        else:
            return False
        
class Plane:
    def __init__(self,normal,point):
        self.normal=normal.normalize()
        self.point=point
        #self.point = MidpointVecs(MidpointVecs(pts[0], pts[1]), pts[2])
##        self.A = self.normal.x
##        self.B = self.normal.y
##        self.C = self.normal.z
##        self.D = -(pts[0].x * (pts[1].y * pts[2].z - pts[2].y * pts[1].z) + pts[1].x * (pts[2].y * pts[0].z - pts[0].y * pts[2].z) + pts[2].x * (pts[0].y * pts[1].z - pts[1].y * pts[0].z))
        
class Scene:
    def __init__(self):
        self.meshes = set()
        self.faces = set()
        self.curves = set()
        self.verts = set()
        
        self.cam2ClipTransform = False
        self.projection = False
        self.BSPTree = False
        
    def getPerspMatrix(self, fovx, aspect, near, far, xOffset=0, yOffset=0):
        near = 0.5
        right = tan(radians(fovx * 0.5)) * near
        left = -right
        top = float(right / aspect)
        bottom = -top
        
        #OpenGL
        return Matrix(  [(2.0 * near)/float(right - left), 0.0, float(right + left)/float(right - left), 0.0],
                        [0.0, (2.0 * near)/float(top - bottom), float(top + bottom)/float(top - bottom), 0.0],
                        [0.0, 0.0, -float(far + near)/float(far - near), -(2.0 * far * near)/float(far - near)],
                        [0.0, 0.0, -1.0, 0.0])
                        
        #Regular (-1 -> 1 Z-depth)
##        return Matrix(  [(2.0 * near)/(right - left), 0.0, -float(right + left)/(right - left), 0.0],
##                        [0.0, (2.0 * near)/(top - bottom), -float(top + bottom)/(top - bottom), 0.0],
##                        [0.0, 0.0, float(far + near)/(far - near), -(2.0 * far * near)/(far - near)],
##                        [0.0, 0.0, 1.0, 0.0]) * Matrix([1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1])

        #DirectX
##        return Matrix(  [(2.0 * near)/float(right - left), 0.0, -float(right + left)/float(right - left), 0.0],
##                        [0.0, (2.0 * near)/float(top - bottom), -float(top + bottom)/float(top - bottom), 0.0],
##                        [0.0, 0.0, float(far)/float(far - near), -(far * near)/float(far - near)],
##                        [0.0, 0.0, 1.0, 0.0])
        
    def getOrthoMatrix(self, aspect , near, far, scale, xOffset=0, yOffset=0):
        top = float(scale/2/aspect)
        bottom = -top
        left = -float(scale/2) 
        right = -left 
        near = .001
        
        r_l = float(right - left)
        t_b = float(top - bottom)
        f_n = float(far - near)
        
        #DirectX (z = 0->1)
        return Matrix(  [2.0/float(right - left), 0.0, 0.0, -float(right + left)/float(right - left)],
                        [0.0, 2.0/float(top - bottom), 0.0, -float(top + bottom)/float(top - bottom)],
                        [0.0, 0.0, 1.0/float(far - near), -float(near)/float(far - near)],
                        [0.0, 0.0, 0.0, 1.0])
                
##        return Matrix(  [2.0/float(right - left), 0.0, 0.0, -float(right + left)/float(right - left)],
##                        [0.0, 2.0/float(top - bottom), 0.0, -float(top + bottom)/float(top - bottom)],
##                        [0.0, 0.0, 2.0/float(far - near), -float(far + near)/float(far - near)],
##                        [0.0, 0.0, 0.0, -1.0])
        
class Mesh:
    def __init__(self,scene,name):
        self.scene = scene
        self.name = name
        self.scene.meshes.add(self)
        self.type = GEOMETRY_TYPE
        self.creaseAngle = 30 #this value gets set according to the Blender's "Auto smooth" value
        self.layers = None
        
        self.edges = set()
        self.faces = set()
        self.curves = list()
        
class Face:   
    def __init__(self,mesh,vertList, materialName = None, normal = False, facing = False, type = GEOMETRY_TYPE):
        self.type = type
        self.scene = mesh.scene
        self.mesh = mesh
        self.verts = vertList
        self.materialName = materialName
        self.normal = normal
        self.facing = facing
        
        #check for the minimum number of verts
        if len(vertList) < 3: 
            raise ValueError, "ERROR - cannot define a face with less than three vertices!"

        #these attributes are user-accessible
        self.VISIBLE = True

        #these all need to be set eventually
        self.bbox = False
        
        self.edges = []
        
        #begin to build connectivity data
        [vert.faces.add(self) for vert in self.verts]
                        
        self.scene.faces.add(self)
        self.mesh.faces.add(self)
        
    def __copy__(self):
        """
        Returns a copy of the face
        """
        newFace = Face(self.mesh, copy(self.verts), self.materialName, self.normal, self.facing)
        newFace.edges = copy(self.edges)
        newFace.VISIBLE = self.VISIBLE
        newFace.type = self.type
        newFace.plane = self.plane
        
        #insert the copied face into the mesh topology
        [vert.faces.add(newFace) for vert in self.verts]
        [edge.faces.append(newFace) for edge in self.edges]
        
        return newFace
    
    def isFace(self):
        #silly type-checking!
        return True
    
    def destroy(self):
        """
        Removes face from mesh and scene
        """
        [edge.faces.remove(self) for edge in self.edges]
        [vert.faces.remove(self) for vert in self.verts]
            
        self.scene.faces.remove(self)
            
        self.mesh.faces.remove(self)
                
    def append(self, verts = [], edges = []):
        for vert in verts:
            self.verts.append(vert)
            vert.faces.add(self)
            
        for edge in edges:
            self.edges.append(edge)
            edge.faces.append(self)
                
    def insert(self, index, verts = [], edges = []):
        """
        Inserts verts or edges into the face
        """
        myVerts = copy(verts)
        myVerts.reverse()
        for vert in myVerts:
            if vert in self.verts: raise "Trying to add a vert that is already present!"
            self.verts.insert(index,vert)
            vert.faces.add(self)
        
        myEdges = copy(edges)
        myEdges.reverse()
        for edge in myEdges:
            if edge in self.edges: raise "Trying to add an edge that is already present!"
            self.edges.insert(index,edge)
            edge.faces.append(self)
                
    def remove(self, verts = [], edges = []):
        """
        Removes verts or edges from face
        """
        for vert in verts:
            self.verts.remove(vert)
            vert.faces.remove(self)
            
        for edge in edges:
            self.edges.remove(edge)
            edge.faces.remove(self)
            
class Curve:
    #A curve in pantograph is a sequence of verts and straight segments (edges)   
    def __init__(self, mesh, vertList, materialName = None):
        self.mesh = mesh
        self.scene = mesh.scene
        self.verts = vertList
        self.materialName = materialName
        
        #these attributes are are user-accessible
        self.VISIBLE = True

        #these all need to be set eventually
        self.edges = []
        
        #begin to build connectivity data
        [vert.curves.add(self) for vert in self.verts]
                        
        self.scene.curves.add(self)
        self.mesh.curves.append(self)
        
    def __copy__(self):
        """
        Returns a copy of the curve
        """
        newCurve = Curve(self.mesh, copy(self.verts), self.materialName)
        newCurve.edges = copy(self.edges)
        newCurve.VISIBLE = self.VISIBLE
        
        #insert the copied face into the mesh topology
        [vert.curves.add(newCurve) for vert in self.verts]
        
        return newCurve
    
    def destroy(self):
        """
        Removes curve from scene
        """
        [vert.curves.remove(self) for vert in self.verts]
            
        self.scene.curves.remove(self)
        self.mesh.curves.remove(self)
        
    def isFace(self):
        #silly type-checking!
        return False
                        
    def append(self, verts = [], edges = []):
        for vert in verts:
            self.verts.append(vert)
            vert.curves.add(self)
            
        for edge in edges:
            self.edges.append(edge)
                
    def insert(self, index, verts = [], edges = []):
        """
        Inserts verts or edges into the curve
        """
        myVerts = copy(verts)
        myVerts.reverse()
        for vert in myVerts:
            if vert in self.verts: raise "Trying to add a vert that is already present!"
            self.verts.insert(index,vert)
            vert.curves.add(self)
        
        myEdges = copy(edges)
        myEdges.reverse()
        for edge in myEdges:
            if edge in self.edges: raise "Trying to add an edge that is already present!"
            self.edges.insert(index,edge)
                
    def remove(self, verts = [], edges = []):
        """
        Removes verts or edges from curve
        """
        for vert in verts:
            self.verts.remove(vert)
            vert.curves.remove(self)
            
        for edge in edges:
            self.edges.remove(edge)
 
class Edge:
    def __init__(self,vertList,mesh):
        self.mesh = mesh
        self.scene = mesh.scene
        self.verts = list(vertList)
        
        self.bbox = BoundingBox(self.verts)
        
        #check for the minimum number of verts
        if len(vertList)<2:
            raise ValueError, "ERROR - cannot define an edge with less than two vertices!"
        
        #attributes
        self.SHARP = False                                                  #Determines whether the edge will become a crease or not
        self.MESH = False                                                   #The edge is an original poly edge, before trinagulation
        self.HIDDEN = False                                                 #The edge has been occluded
        self.SILHOUETTE = False                                             #Silhouette edge, between front-facing and back-facing polygons
        self.DRAWN = False                                                  #Flag that indicates that the edge has been drawn
        self.CLIPPED = False                                                #Indicates the edge has been visited by the 2d clipping algorithm
        self.VISIBLE = True                                                 ## Why is this needed in addition to HIDDEN?  Its confusing...
        self.INTERSECTION = False                                           ## This feature doesn't exist yet!!
        self.CUT = False                                                    #An edge that has been clipped by a Cutaway or Vignette
        self.CUTISVISIBLE = False                                           #determines whether a cut edge should be shown as a Cut or Silhouette
        
        #these are additional topology nodes which are set during polyline-making
        #self.hardConnections = set()
        #self.softConnections = set()
        
        #these all need to be set eventually
        self.faces = list()

        #begin to build connectivity data
        [vert.edges.add(self) for vert in self.verts]
            
        self.mesh.edges.add(self)
        
    def __copy__(self):
        """
        Returns a copy of the edge
        """
        newEdge = Edge(copy(self.verts),self.mesh)
        newEdge.faces = copy(self.faces)
        
        newEdge.SHARP = self.SHARP
        newEdge.MESH = self.MESH
        newEdge.HIDDEN = self.HIDDEN
        newEdge.SILHOUETTE = self.SILHOUETTE
        newEdge.DRAWN = self.DRAWN
        newEdge.VISIBLE = self.VISIBLE
        newEdge.INTERSECTION = self.INTERSECTION
        newEdge.CUT = self.CUT 
                  
        [vert.edges.add(newEdge) for vert in self.verts]
        [face.edges.append(newEdge) for face in self.faces]
        
        return newEdge
    
    def matchProperties(self, newEdge):
        """
        Matches the other edge's properties to the edge
        """
        newEdge.SHARP = self.SHARP
        newEdge.MESH = self.MESH
        newEdge.HIDDEN = self.HIDDEN
        newEdge.SILHOUETTE = self.SILHOUETTE
        newEdge.DRAWN = self.DRAWN
        newEdge.VISIBLE = self.VISIBLE
        newEdge.INTERSECTION = self.INTERSECTION
        newEdge.CUT = self.CUT 

    def destroy(self):
        [face.edges.remove(self) for face in self.faces]
        [vert.edges.remove(self) for vert in self.verts]
        
        self.mesh.edges.remove(self)
        
    def collapse(self):
        """
        removes the second vert on the edge, collapsing all connections
        onto the first vert
        """
        debug('COLLAPSED!')
        keepVert = self.verts[0]
        destroyVert = self.verts[1]
        
        #add the destroyed vert's connections to the kept vertice
        keepVert.edges = keepVert.edges.union(destroyVert.edges)                
        keepVert.faces = keepVert.faces.union(destroyVert.faces)
        
        #add the kept vertice to the destroyed vert's connections and remove the destroyed vert
        for edge in destroyVert.edges:
            if not keepVert in edge.verts:
                edge.verts.append(keepVert)
                try:
                    edge.verts.remove(destroyVert)  
                
                except:
                    if len(edge.verts != 2): raise "Oops!"
                
        for face in destroyVert.faces:
            if not keepVert in face.verts:
                face.verts.insert(face.verts.index(destroyVert), keepVert)
                face.verts.remove(destroyVert)                
        
        #remove the deleted vertice from the mesh and the scene
        self.scene.verts.remove(destroyVert)
        
        #remove the edge
        [face.edges.remove(self) for face in self.faces]
        [vert.edges.remove(self) for vert in self.verts]
        
        self.mesh.edges.remove(self)        
        
    def append(self, verts = []):
        """
        Appends verts to an edge
        """
        for vert in verts:
            self.verts.append(vert)
            vert.edges.add(self)
            
        if len(self.verts) > 2:
            debug(self.verts)
            raise ValueError, "Cannot have more than two verts in an edge!"
                                
    def remove(self, verts = []):
        """
        Removes verts from edge
        """
        for vert in verts:
            self.verts.remove(vert)
            vert.edges.remove(self)
                    
class Vert:    
    def __init__(self, scene, loc = None, loc2d = None):
        self.scene = scene
        
        self.loc = loc
        self.loc2d = loc2d
        
        #these all need to be set eventually
        self.edges = set()
        self.faces = set()
        self.curves = set()
        
        #these will be used to track sharp or soft corners, so Regions can have the same softness as Polylines
        self.SHARP = True                                                   #Default is sharp: new verts with unknown softness are sharp until verified
        self.SOFT = False
        
        self.scene.verts.add(self)
        
    def __hash__(self):
        return self.loc.__hash__()
    
    def compareLoc2d(self, otherLoc2d):
        """
        compares an unstructured 2d location [x,y] with the vert's loc2d
        returns True if the difference is less than EPSILON2D
        """
        if ((abs(self.loc2d.x - otherLoc2d[0]) < EPSILON2D) and (abs(self.loc2d.y - otherLoc2d[1]) < EPSILON2D)):
            return self
        
        else:
            return False
        
class BSPTree:
    def __init__(self,faceList, curveList):
        global PROCESSED_FACES
        global TOTAL_FACES
        
        self.tree = [None, None, None]
        self.root = self.tree[0]
        self.front = self.tree[1]
        self.back = self.tree[2]
        
        if len(faceList) > 1:
            self.makeNewBranch(faceList, curveList)
            
        else:
            self.root = faceList + curveList
        
    def backToFront(self, depth):
        global MAX_DEPTH
        myList = []
        if self.back:
            myList.extend(self.back.backToFront(depth + 1))
            
        myList.extend(self.root)
        
        if depth > MAX_DEPTH: MAX_DEPTH = depth
        
        if self.front:
            myList.extend(self.front.backToFront(depth + 1))
        
        return myList
        
    def makeNewBranch(self, faceList, curveList):
        print ".",
        
        global TOTAL_FACES
        
        if not faceList: #this case handles places where we have curves but no splitting faces
            self.root = curveList
        
        else:
            numTests = 5
            if numTests > len(faceList): numTests = len(faceList)
            
            bestScore = False
            bestNode = False
            
            classifyDict = dict()
            candidateList = []
            frontList = []
            backList = []
            onList = []
            intersectList = []
            degenerate = True
            
            #select a face for the splitting face
            ### Here we could add in a test for degenerate (spherical) meshes!

            for test in range(numTests):
                candidate = faceList[test]
                myFrontList = []
                myBackList = []
                myOnList = []
                myIntersectList = []
                
                #make a dict with the classifications of all the verts according to the splitting face
                myClassifyDict = dict()
                
                #classify all the faces according to the splitting face (setting classifyDict in the process)
                classifyList = [classifyFaceFace(face, candidate, myClassifyDict) for face in faceList[:test] + faceList[test+1:]]
                
                myFrontList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == IN_FRONT]
                myBackList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == BEHIND]
                myOnList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == ON]
                myIntersectList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == INTERSECTING]
                                        
                myScore = len(myIntersectList)
                
                #If all of the faces end up on one side or the other, we have a degenerate condition
                if (len(classifyList) < RECURSION_LIMIT) or (myFrontList and myBackList) :
                    degenerate = False
                
                if not bestNode or (myScore < bestScore):
                    bestScore = myScore
                    bestNode = candidate
                    frontList = myFrontList
                    backList = myBackList
                    intersectList = myIntersectList
                    onList = myOnList
                    classifyDict = myClassifyDict
            
            if not degenerate:
                self.root = [bestNode]
                
            else:
                ### replace this with a true solution to the degeneracy
                self.root = [bestNode]#raise("We have a degenerate condition developing!")
                        
            #put the lists in the appropriate branch of the tree
            if intersectList:
                ##!!! We can't handle exceptions here! !!!
                splitList = [splitFaceFace(face, self.root[0], classifyDict) for face in intersectList]
                
                for (split,face) in zip(splitList,intersectList):
                    
                    if split == IN_FRONT: 
                        frontList.append(face)
                    
                    elif split == BEHIND: 
                        backList.append(face)
                        
                    elif split == ON:
                        onList.append(face)
                        
                    elif split:                 
                        frontList.append(split[0])
                        backList.append(split[1])
                        
                        TOTAL_FACES += 1                    
                    ##!!! What would cause this final case? !!!
                    else:
                        debug("*******We have an incorrect split!********")
                        backList.append(face)
                
            #split the curves in curveList by the root node
            frontCurveList = []
            backCurveList = []
            onCurveList = []

            if curveList:
                curveSplitList = [splitCurveFace(curve, self.root[0]) for curve in curveList]
                
                for (split, curve) in zip(curveSplitList, curveList):
                    if split == IN_FRONT:
                        frontCurveList.append(curve)
                        
                    elif split == BEHIND:
                        backCurveList.append(curve)
                        
                    elif split == ON:
                        onCurveList.append(curve)
                        
                    elif split:
                        frontCurveList.extend(split[2])
                        onCurveList.extend(split[1])
                        backCurveList.extend(split[0])
                        
            #add the faces and curves to the proper branches of the tree                    
            self.root.extend(onList)
            self.root.extend(onCurveList)
                                
            if frontList or frontCurveList:
                self.front = BSPTree(frontList, frontCurveList)
            
            if backList or backCurveList:
                self.back = BSPTree(backList, backCurveList)
                            
#---2D GEOMETRY FUNCTIONS---#000000#FFFFFF--------------------------------------                
def isOnSegment(vert, segment):
    '''
    determines if a location(vector) is on given edge
    from http://www.geometryalgorithms.com/Archive/algorithm_0102/algorithm_0102.htm
    '''
    v = segment[1].loc2d - segment[0].loc2d
    w = vert.loc2d - segment[0].loc2d
    c1 = w * v
    if c1 <= EPSILON2D:
        if distance(vert.loc2d, segment[0].loc2d) < EPSILON2D: return True
        else: return False
    
    c2 = v * v
    if c2 <= c1:
        if distance(vert.loc2d, segment[1].loc2d) < EPSILON2D: return True
        else: return False
    
    b = c1 / c2
    pointB = segment[0].loc2d + (b * v)
    if distance(vert.loc2d, pointB) < EPSILON2D: return True
    else: return False
    
def perpendicularProduct(u, v):
    """
    returns the 2d perpendicular product of two vectors
    """
    return ((u).x * (v).y - (u).y * (v).x)
    
def intersectEdgeSegment(edge, segment):
    """
    returns a tuple (mu, intersection point)
    see http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/example.cpp
    """
    #first, discard cases where intersection is impossible
    if ((edge.verts[0].loc2d.x < segment[0][0]) and (edge.verts[0].loc2d.x < segment[1][0]) and (edge.verts[1].loc2d.x < segment[0][0]) and (edge.verts[1].loc2d.x < segment[1][0])):
        return False
    
    if ((edge.verts[0].loc2d.y < segment[0][1]) and (edge.verts[0].loc2d.y < segment[1][1]) and (edge.verts[1].loc2d.y < segment[0][1]) and (edge.verts[1].loc2d.y < segment[1][1])):
        return False
    
    if ((edge.verts[0].loc2d.x > segment[0][0]) and (edge.verts[0].loc2d.x > segment[1][0]) and (edge.verts[1].loc2d.x > segment[0][0]) and (edge.verts[1].loc2d.x > segment[1][0])):
        return False
    
    if ((edge.verts[0].loc2d.y > segment[0][1]) and (edge.verts[0].loc2d.y > segment[1][1]) and (edge.verts[1].loc2d.y > segment[0][1]) and (edge.verts[1].loc2d.y > segment[1][1])):
        return False
        
    #proceed with finding an intersection pt
    denominator =   ((segment[1][1] - segment[0][1]) * (edge.verts[1].loc2d.x - edge.verts[0].loc2d.x)) - \
                    ((segment[1][0] - segment[0][0]) * (edge.verts[1].loc2d.y - edge.verts[0].loc2d.y))
                    
    numeratorA =    ((segment[1][0] - segment[0][0]) * (edge.verts[0].loc2d.y - segment[0][1])) - \
                    ((segment[1][1] - segment[0][1]) * (edge.verts[0].loc2d.x - segment[0][0]))
                    
    numeratorB =    ((edge.verts[1].loc2d.x - edge.verts[0].loc2d.x) * (edge.verts[0].loc2d.y - segment[0][1])) - \
                    ((edge.verts[1].loc2d.y - edge.verts[0].loc2d.y) * (edge.verts[0].loc2d.x - segment[0][0]))

    if (denominator == 0):
        return False #PARALLEL OR COINCIDENT
    
    ua = numeratorA / denominator
    ub = numeratorB / denominator
    
    if ((ua >= 0.0) and (ua <= 1.0) and (ub >= 0.0) and (ub <= 1.0)):
        #get the intersection point
        intersectionPoint = Vector(0,0)
        intersectionPoint.x = edge.verts[0].loc2d.x + ua*(edge.verts[1].loc2d.x - edge.verts[0].loc2d.x)
        intersectionPoint.y = edge.verts[0].loc2d.y + ua*(edge.verts[1].loc2d.y - edge.verts[0].loc2d.y)
        
        #ua should be the mu value for the intersection point
        return (ua, intersectionPoint) #INTERSECTING
    
    return False #NOT_INTERSECTING - the intersection point is not on the line

def clipEdgeRegion(edge, region):
    """
    clips edge against a region, returning a tuple of edge-segments, marked as Hidden where appropriate
    """
    #in the case of an empty region, just return the edge itself
    if len(region.polygon)<1:
        return [edge]
    
    #check if the edge's verts are entirely inside the region
    maybeClipped = False
    
    if region.polygon.isInside(edge.verts[0].loc2d.x, edge.verts[0].loc2d.y) and region.polygon.isInside(edge.verts[1].loc2d.x, edge.verts[1].loc2d.y):
        maybeClipped = True
    
    #get a list of segments in the region, discarding any whose bounding boxes don't overlap the edge's
    segmentList = [(point, nextPoint) for contour in region.polygon for (point, nextPoint) in zip(contour, (contour[1:] + contour[:1]))]
    
    if not segmentList:
        
        return [edge]                                                       #if we have an edge with no intersections at all, we just return the edge
    
    #get the intersection points of the list and the edge, and rank them by mu (aka decorate and sort)
    intersectPoints = [intersectEdgeSegment(edge, segment) for segment in segmentList]
    intersectPoints = filter(None, intersectPoints)                        #discard non-intersections by removing False results
    
    if not intersectPoints:
        if maybeClipped:
            edge.HIDDEN = True #with no intersections, and both verts inside, it must be hidden
            
        return [edge]                                                       #if we have an edge with no intersections at all, we just return the edge
    
    else:
        intersectPoints.sort()                                              #sort the intersection points by their parametric distance along the edge
        
    #make new verts, and put them in a list
    vertList = [Vert(edge.scene, loc2d = loc) for (index, loc) in intersectPoints]
    vertList.insert(0,edge.verts[0])
    vertList.append(edge.verts[1])
        
    edgeList = list()
    
    [edgeList.append(Edge([vert, nextVert], edge.mesh)) for (vert, nextVert) in zip(vertList[:-1], vertList[1:])]
        
    #duplicate flags from the original edge in the new edges
    [edge.matchProperties(newEdge) for newEdge in edgeList]
        
    #figure out if the first point is inside the region's polygon
    #check a point mid-way between the first and second verts - this gets around Polygon's uncertainty about boundary points
    #midwayPoint = MidpointVecs(edge.verts[0].loc2d, edge.verts[1].loc2d)    
    if region.isInside(vertList[0].loc2d):#midwayPoint):
        isInside = True
        
    else:
        isInside = False
            
    #mark the new edges according to Hidden or not
    for newEdge in edgeList:
        if isInside: 
            newEdge.HIDDEN = True
        else: 
            newEdge.HIDDEN = False
        isInside = not isInside

    #destroy original edge
    edge.destroy()
    
    #return the list of new edges
    return edgeList
                
def combinePolylines(drawing, polylineDict):
    """
    combine single segments into longer polylines    
    myPolylineDict is a dictionary by (linetype, list of segments)
    """
    for key in polylineDict:                                                #We know that these edges are all of the same type...
        if  (key != 'MESH'):                                                #no MESH edges are connected
            edgeSet = set(polylineDict[key])
            polylineList = list()
        
            while edgeSet:
                myEdge = edgeSet.pop()
                myPolyline = Polyline([myEdge])
                
                #here we extend the polyline as far as possible in one direction, then flip the list, and extend in the other direction
                if edgeSet:                                                 #if we have more segments, try to add them
                    currentVert = nextVert = myPolyline.verts[-1]
                    if nextVert:
                        nextVert = None
                        for edge in currentVert.edges:
                            if edge in edgeSet:
                                edgeSet.remove(edge)
                                nextVert = [vert for vert in edge.verts if vert != currentVert][0]
                                myPolyline.append(vertList = [nextVert])
                                currentVert = nextVert
                                break                
                            
                    myPolyline.verts.reverse()
                    
                    currentVert = nextVert = myPolyline.verts[-1]
                    if nextVert:
                        nextVert = None
                        for edge in currentVert.edges:
                            if edge in edgeSet:
                                edgeSet.remove(edge)
                                nextVert = [vert for vert in edge.verts if vert != currentVert][0]
                                myPolyline.append(vertList = [nextVert])
                                currentVert = nextVert
                                break
                            
                    myPolyline.verts.reverse()
                    
                polylineList.append(myPolyline)
                
        else:                                                               #we need to make one-segment polylines out of the individual MESH edges
            polylineList = [Polyline([edge]) for edge in polylineDict[key]]
            
        polylineDict[key] = polylineList
                                    
def clipObjects(drawing, object, myPolylineDict, isHidden):
    """
    determine if the object is a face or a curve, 
    clip the object against the appropriate clipping regions,
    and classify the fills and segments into the appropriate dicts
    """
    global CLIP_COUNTER
    if CLIP_COUNTER%100 == 0:
        print ".",
    CLIP_COUNTER+=1
        
    scene = drawing.scene
    #DETERMINE IF WE HAVE A FACE OR A CURVE   
    if object.isFace():#if we have a regular face with edges
        myRegion = Region(scene, vertList = object.verts)
        
        #Clip myRegion and its edges against the clipping region if necessary:
        #face covered: all edges are hidden
        if isHidden:
            for edge in object.edges:
                if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT):
                    myPolylineDict['HIDDEN'].append( edge )
                    
                    edge.CLIPPED = True
                    
        elif drawing.lineClippingRegion.covers(myRegion):
            for edge in object.edges:
                if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT):
                    myPolylineDict['HIDDEN'].append( edge )
                    
                    edge.CLIPPED = True
                    
        #face partially covered: clip the edges    
        elif drawing.lineClippingRegion.overlaps( myRegion ):
            myEdgeList = copy(object.edges)#otherwise, we screw things up when we add split edges to object.edges
            for edge in myEdgeList:
                if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT or edge.MESH):
                    myClippedList = clipEdgeRegion(edge, drawing.lineClippingRegion)
                    
                    for newEdge in myClippedList:
                        if newEdge.HIDDEN and (newEdge.SILHOUETTE or newEdge.SHARP or newEdge.CUT): myPolylineDict['HIDDEN'].append( newEdge )
                        elif newEdge.SILHOUETTE: myPolylineDict['SILHOUETTE'].append( newEdge )
                        elif newEdge.SHARP: myPolylineDict['CREASE'].append( newEdge )
                        elif newEdge.CUT: myPolylineDict['CUT'].append( newEdge )
                        elif not newEdge.HIDDEN and newEdge.MESH: myPolylineDict['MESH'].append( newEdge )
                        
                        edge.CLIPPED = True
                                                                        
        #neither face nor its edges are clipped: both are added to the drawing queue
        else:
            for edge in object.edges:
                if not edge.CLIPPED:
                    if edge.SILHOUETTE: myPolylineDict['SILHOUETTE'].append( edge )
                    elif edge.SHARP: myPolylineDict['CREASE'].append( edge )
                    elif edge.CUT: myPolylineDict['CUT'].append( edge )
                    elif edge.MESH: myPolylineDict['MESH'].append( edge )
                    
                    edge.CLIPPED = True
                    
        #clip the regions for fills
        if drawing.settings.USE_ALPHA:
            if (isHidden or drawing.fillClippingRegion.covers( myRegion )):
                myRegion = Region(scene)
            
            elif drawing.fillClippingRegion.overlaps( myRegion ):
                myRegion = myRegion - drawing.fillClippingRegion
        
        else:
            if (isHidden or drawing.lineClippingRegion.covers( myRegion )):
                myRegion = Region(scene)                
            
            elif drawing.lineClippingRegion.overlaps( myRegion ):
                myRegion = myRegion - drawing.lineClippingRegion
            
        #add myRegion to the proper material Region and to the clippingRegion
        if (drawing.settings.USE_ALPHA) and (drawing.settings.pens[object.materialName].attributeDict["FILLCOLOR"].a < 1.0):#we have a transparent fill: transparent fills do not occlude other fills
            drawing.transparentFillDict[object.materialName][object.mesh] = (drawing.transparentFillDict[object.materialName][object.mesh] + myRegion)
            drawing.lineClippingRegion = drawing.lineClippingRegion + myRegion
            
            if not drawing.currentTransparentRegion: #this is the top-most transparent fill Region
                drawing.currentTransparentRegion = [object.materialName, myRegion]
                
            else:#if we need to make a new transparent region, copy the current transparent region into the queue and start a new region
                if myRegion.overlaps(drawing.currentTransparentRegion[1]):
                    drawing.transparentFillQueue.append(copy(drawing.currentTransparentRegion))
                    drawing.currentTransparentRegion = [object.materialName, myRegion]
                    
                elif object.materialName == drawing.currentTransparentRegion[0]:
                    drawing.currentTransparentRegion[1] = drawing.currentTransparentRegion[1] + myRegion
                    
                else:
                    drawing.transparentFillQueue.append(copy(drawing.currentTransparentRegion))
                    drawing.currentTransparentRegion = [object.materialName, myRegion]
                                                
        else:#we have an opaque material
            if not drawing.fillDict[object.materialName][object.mesh]:      #If we haven't used this Region, it must be initialized
                drawing.fillDict[object.materialName][object.mesh] = Region(scene)
                
            drawing.fillDict[object.materialName][object.mesh] = drawing.fillDict[object.materialName][object.mesh] + myRegion
            drawing.lineClippingRegion = drawing.lineClippingRegion + myRegion

            if drawing.settings.USE_ALPHA: 
                drawing.fillClippingRegion = drawing.fillClippingRegion + myRegion
    
    else: #we have a curve: clip edges and add them to their appropriate polyline dict
        for edge in object.edges:
            myClippedList = clipEdgeRegion(edge, drawing.lineClippingRegion)
                        
            for newEdge in myClippedList:
                if newEdge.HIDDEN:
                    myPolylineDict['HIDDEN'].append( newEdge )
                else:
                    myPolylineDict['CURVE'].append( newEdge )
                    
#---2D GEOMETRY CLASSES---#000000#FFFFFF--------------------------------------                
class Region:
    """
    A collection of contours, bounded by verts
    A new region can only be initialized with a single (outer) contour
    """
    def __init__(self, scene, vertList = []):
        self.scene = scene
            
        if vertList:
            self.polygon = Polygon([(vert.loc2d.x, vert.loc2d.y) for vert in vertList])
            
        else:
            self.polygon = Polygon()
            
    def __add__(self, otherRegion):
        newRegion = copy(self)
        newRegion.polygon = self.polygon + otherRegion.polygon
        
        return newRegion
        
    def __sub__(self, otherRegion):
        newRegion = copy(self)
        newRegion.polygon = self.polygon - otherRegion.polygon
        
        return newRegion
    
    def overlaps(self, otherRegion):
        if self.polygon.overlaps(otherRegion.polygon):
            return True
        
        else:
            return False
        
    def covers(self, otherRegion):
        if self.polygon.covers(otherRegion.polygon):
            return True
        
        else:
            return False
        
    def isInside(self, loc):
        if self.polygon.isInside(loc.x, loc.y):
            return True
        
        else:
            return False
        
    def draw(self, context):
        for contour in self.polygon:
            context.move_to(contour[0][0], contour[0][1])
            
            for point in contour[1:]:
                context.line_to(point[0], point[1])
                
            context.close_path()
        
    def drawSWF(self, shape, movie, color):
        myShape = shape
        
        myShape.setLine(0,0,0,0,0)
        myShape.setRightFill(myShape.addFill(color.r* 255,color.g* 255,color.b* 255,color.a* 255))
                
        for contour in self.polygon:
            myShape.movePenTo(contour[0][0], contour[0][1])
            
            for point in contour[1:]:
                myShape.drawLineTo(point[0], point[1])
                
            myShape.drawLineTo(contour[0][0], contour[0][1])
            
        return [myShape]

    def outline(self, context):
        for contour in self.polygon:
            context.move_to(contour[0][0], contour[0][1])
            
            for point in contour[1:]:
                context.line_to(point[0], point[1])
                
            context.close_path()
        
    def outlineSWF(self, shape, movie, width, color):
        myShape = self.shape
        
        myShape.setLine(width, color.r* 255,color.g* 255,color.b* 255,color.a* 255)
                
        for contour in self.polygon:
            myShape.movePenTo(contour[0][0], contour[0][1])
            
            for point in contour[1:]:
                myShape.drawLineTo(point[0], point[1])
                
            myShape.drawLineTo(contour[0][0], contour[0][1])
        
        return [myShape]
                                    
class Polyline:
    """
    an ordered list of edges
    """    
    def __init__(self, edgeList = None, vertList = None):
        self.closed = False                                                 #tells us whether the polyline is closed or open
        self.verts = list()
        self.edges = list()
        
        self.edges = edgeList
        
        if len(self.edges) == 1:
            self.verts = self.edges[0].verts
            
        else:
            for (edge, nextEdge) in zip(self.edges[:-1], self.edges[1:]):
                if edge.verts[0] in nextEdge.verts:
                    self.verts.append[edge.verts[1]]
                    self.verts.append[edge.verts[0]]
                
                elif edge.verts[1] in nextEdge.verts:
                    self.verts.append[edge.verts[0]]
                    self.verts.append[edge.verts[1]]
                
                else:
                    raise "Trying to define a polyline with a broken chain of edges!"
                
            if self.verts[0] == self.verts[-1]:                              #If we have the same vert at the beginning and end, we have a closed polyline
                self.closed = True
                self.verts.pop()
        
    def draw(self, context):
        context.move_to(self.verts[0].loc2d.x, self.verts[0].loc2d.y)
        for vert in self.verts[1:]:
            context.line_to(vert.loc2d.x, vert.loc2d.y)
        
    def drawSWF(self, shape, movie, width, color):
        myShape = shape
        myShape.setLine(width, color.r * 255,color.g * 255,color.b * 255,color.a * 255)
                
        myShape.movePenTo(self.verts[0].loc2d.x, self.verts[0].loc2d.y)
        for vert in self.verts[1:]:
            myShape.drawLineTo(vert.loc.x, vert.loc.y)
            
        return [myShape]
                           
    def append(self, edgeList = [], vertList = []):
        if edgeList:
            if self.verts[-1] not in edgeList[0].verts:
                raise("Trying to append unconnected group of edges to polyline!")
            
            else:
                for edge in edgeList:
                    if self.verts[-1] == edge.verts[0]:
                        self.verts.append(edge.verts[1])
                    
                    elif self.verts[-1] == edge.verts[1]:
                        self.verts.append(edge.verts[0])
                        
                    else:
                        raise("Trying to define a polyline with a broken chain of edges!")
                        
        elif vertList:
            for vert in vertList:
                self.verts.append(vert)
    
    def extend(self, otherPolyline):
        if otherPolyline.verts[0] == self.verts[-1]:                        #end to start connection
            self.verts.extend(otherPolyline.verts)
            self.edges.extend(otherPolyline.edges)
            
        elif otherPolyline.verts[0] == self.verts[0]:                       #start to start connection
            self.verts.reverse()
            self.edges.reverse()
            self.verts.extend(otherPolyline.verts)
            self.edges.extend(otherPolyline.edges)
            
        elif otherPolyline.verts[-1] == self.verts[-1]:                     #end to end connection
            otherPolyline.verts.reverse()
            otherPolyline.edges.reverse()
            self.verts.extend(otherPolyline.verts)
            self.edges.extend(otherPolyline.edges)
            
        else:
            raise("Trying to connect two polylines which do not connect!")
            
#---PIPELINE FUNCTIONS (IN ORDER OF APPLICATION)---#000000#FFFFFF--------------------------------------
def blender2Pantograph(drawing, frame = None):
    """
    bring in mesh data from Blender
    """
    #a little Blender housekeeping
    if Blender.Window.EditMode(): Blender.Window.EditMode(0)
    
    #set up the scene and rendering context
    scene = drawing.scene  
    blenderScene = Blender.Scene.GetCurrent()
    blenderRender = blenderScene.getRenderingContext()
    
    #set the proper frame, if necessary
    try:
        blenderRender.currentFrame(frame)
        progress('Rendering frame %i...' % frame)
    except:
        progress('Rendering current frame...')
    
    #set up the camera location and matrix
    blenderCamera = blenderScene.objects.camera#getCurrentCamera()
    progress('Setting up camera:')
    progress('        near clip: %f' % blenderCamera.getData().clipStart)
    progress('         far clip: %f' % blenderCamera.getData().clipEnd)
    progress('     fov(degrees): %f' % blenderCamera.getData().angle)
    progress('            scale: %f' % blenderCamera.getData().scale)

    scene.cameraMatrix = blenderCamera.getInverseMatrix()
    
    #set up the FRUSTUM
    drawing.WIDTH=float(blenderRender.imageSizeX())
    drawing.HEIGHT=float(blenderRender.imageSizeY())
    
    scene.aspect = drawing.WIDTH / drawing.HEIGHT
    #scene.fovy = blenderCamera.getData().angle/scene.aspect
    scene.fovx = blenderCamera.getData().angle
    scene.near = blenderCamera.getData().clipStart
    scene.far = blenderCamera.getData().clipEnd
    scene.scale = blenderCamera.getData().scale
    
    if blenderCamera.getData().type == 'ortho':
        scene.cam2ClipTransform = scene.getOrthoMatrix(scene.aspect,scene.near,scene.far,scene.scale)
        scene.projection = 'ortho'
    
    elif blenderCamera.getData().type == 'persp':
        scene.cam2ClipTransform = scene.getPerspMatrix(scene.fovx,scene.aspect,scene.near,scene.far)
        scene.projection = 'persp'
    
    debug('World-space camera location:',blenderCamera.loc)
    debug('Camera-space camera location:',Vector(blenderCamera.loc).resize4D() * scene.cameraMatrix)
        
    scene.camera = Vector(blenderCamera.loc).resize4D() * scene.cameraMatrix
    
    #get the current world background color(s)
    blenderWorld = Blender.World.GetCurrent()
    drawing.zenithColor = RGBA(blenderWorld.getZen()[0], blenderWorld.getZen()[1], blenderWorld.getZen()[2], 1)
    drawing.horizonColor = RGBA(blenderWorld.getHor()[0], blenderWorld.getHor()[1], blenderWorld.getHor()[2], 1)
        
    #get the scene's objects
    blenderObjList=blenderScene.objects
    proxyObj = Blender.Object.New('Mesh','temp')
    
    for blenderObj in blenderObjList:
        if len(intersect(blenderObj.layers, blenderScene.layers)) == 0:
            progress("Object %s is not visible..." % blenderObj.name)
        
        elif    blenderObj.type == 'Mesh' or \
                blenderObj.type == 'Surf' or \
                blenderObj.type == 'MBall' or \
                blenderObj.type == 'Curve' or \
                blenderObj.type == 'Mesh' or \
                blenderObj.type == 'Text':
                
            progress("Processing object %s" % blenderObj.name)
            
            blenderMesh = Blender.Mesh.New()
            blenderMesh.getFromObject(blenderObj, 0, 1)
            
            Blender.Mesh.Mode(Blender.Mesh.SelectModes['FACE'])
            
            proxyObj.link(blenderMesh)
            blenderScene.objects.link(proxyObj)
            
            vertDict={}
            blenderMeshEdges = []
            mesh = Mesh(scene, blenderObj.name)
            #get the layers from the Blender object
            mesh.layers = set(blenderObj.layers)
            
            #set the mesh crease angle to the blender mesh's smoothing angle
            mesh.creaseAngle = blenderMesh.degr
            
            blenderMaterials = blenderMesh.materials
            
            progress("Triangulating faces...")
            
            blenderMeshEdges = [edge for edge in blenderMesh.edges]#this will give us a list of the edges BEFORE triangulation
                
            for blenderFace in blenderMesh.faces:
                blenderFace.sel = 1
            
            try:        
                blenderMesh.quadToTriangle()
            
            except:
                debug("No triangulation is required...")
            
            blenderMesh.update()
            proxyObj.makeDisplayList()
            Blender.Window.RedrawAll()
            
            #transform the mesh to get the world coordinates
##            objMatrix = blenderObj.getMatrix()
##            myTransform = objMatrix * scene.cameraMatrix * scene.cam2ClipTransform
##            blenderMesh.transform(myTransform)
            
            #VERTS####################
            progress("Processing %i vertices..." % len(blenderMesh.verts))
            
            #Get the World matrix of the Object
            objMatrix = blenderObj.getMatrix().copy().resize4x4()
            
            #Object World Matrix x Camera Inverse Matrix
            myTransform = objMatrix * scene.cameraMatrix * scene.cam2ClipTransform
            
            def normalize(vector):
                return (vector.x/vector.w, vector.y/vector.w, vector.z/vector.w)
            
            vertLocList = [Vector(vert.co).resize4D() * myTransform for vert in blenderMesh.verts]
            vertList = [Vert(scene, loc = loc) for loc in vertLocList]
##            vertLocList = (Vector(vert.co).resize4D() for vert in blenderMesh.verts)
##            vertList = (Vert(scene, loc = loc) for loc in vertLocList)
            
            setItem = vertDict.__setitem__ 
            [setItem(blenderVert, vert) for (blenderVert, vert) in zip(blenderMesh.verts, vertList)]
                        
            #EDGES####################                
            progress("Processing %i edges..." % len(blenderMesh.edges))
            edgeList = [Edge(vertList, mesh) for vertList in [(vertDict[edge.v1], vertDict[edge.v2]) for edge in blenderMesh.edges]]
                      
            for (blenderEdge, edge) in zip(blenderMesh.edges, edgeList):
                if blenderEdge.flag & Blender.Mesh.EdgeFlags.SHARP:
                    edge.SHARP = True
                
                elif blenderEdge in blenderMeshEdges:
                    edge.MESH = True
                    
            #FACES/CURVES####################                    
            if len(blenderMesh.faces) > 0: 
                progress ("Processing %i faces..."% len(blenderMesh.faces))
                 
                faceVertLists = [[vertDict[face.verts[0]],vertDict[face.verts[1]],vertDict[face.verts[2]]] for face in blenderMesh.faces]
                
                faceList = [Face(mesh,vertList) for vertList in faceVertLists]
                
                [computeNormal(face) for face in faceList]
                
                classifyList = (classifyPointPlane(scene.camera, face.plane) for face in faceList)
                
                for (classify, face) in zip(classifyList, faceList):
                    if classify == IN_FRONT:
                        face.facing = FRONT_FACING
                        
                    else:
                        face.facing = BACK_FACING
                        
                #set bounding box for each face
                bboxList = (BoundingBox(face.verts) for face in faceList)
                for (bbox,face) in zip(bboxList, faceList):
                    face.bbox = bbox                
            else:                
                #this must mean we have a curve (or a mesh with edges but no faces)
                progress ("Processing curve...")
                mesh.curves = [Curve(mesh, vertDict.values())]                          

            #MATERIALS####################
            progress ("Processing materials...")
            if mesh.faces:
                for (blenderFace, face) in zip(blenderMesh.faces, faceList):
                    try:
                        myMaterial = blenderMaterials[blenderFace.mat]
                        
                    except:
                        myMaterial = None #this covers objects with no material
                        
                    if myMaterial and not myMaterial.name in drawing.settings.materialDefaults.keys():#convert the material to an RGBA
                        drawing.settings.materialDefaults[myMaterial.name] =    RGBA(  myMaterial.R, 
                                                                                    myMaterial.G, 
                                                                                    myMaterial.B,
                                                                                    myMaterial.alpha )
                    
                    if myMaterial:                                                                        
                        if myMaterial.name == "CUT" or myMaterial.name == "Cut" or myMaterial.name == "cut":#for cutting faces
                            face.type = CUTTING_TYPE
                            face.mesh.type = CUTTING_TYPE
                            face.materialName = 'DEFAULT'
                            
                        elif myMaterial.name == "VIGNETTE" or myMaterial.name == "Vignette" or myMaterial.name == "vignette":#for vignette faces
                            face.type = VIGNETTE_TYPE
                            face.mesh.type = VIGNETTE_TYPE
                            face.materialName = 'DEFAULT'
                            
                        elif myMaterial:
                            face.materialName = myMaterial.name  
                                          
                    else: #this covers objects with no material
                        face.materialName = 'DEFAULT'
                        
            elif mesh.curves:
                blenderCurve = blenderObj.data
                for curve in mesh.curves:
                    try:
                        myMaterial = blenderCurve.getMaterials()[0]
                        
                    except:
                        myMaterial = None #this covers objects with no material
                        
                    if myMaterial and not myMaterial.name in drawing.settings.materialDefaults.keys():#convert the material to an RGBA
                        drawing.settings.materialDefaults[myMaterial.name] =    RGBA(  myMaterial.R, 
                                                                                    myMaterial.G, 
                                                                                    myMaterial.B,
                                                                                    myMaterial.alpha )
                    
                    if myMaterial:                                                                        
                        if myMaterial.name == "CUT" or myMaterial.name == "Cut" or myMaterial.name == "cut":
                            #curves cannot be cutting faces, so this is set to DEFAULT
                            curve.materialName = 'DEFAULT'
                            
                        elif myMaterial.name == "VIGNETTE" or myMaterial.name == "Vignette" or myMaterial.name == "vignette":
                            #curves cannot be vignette faces, so this is set to DEFAULT
                            curve.materialName = 'DEFAULT'
                            
                        elif myMaterial:
                            curve.materialName = myMaterial.name  
                                          
                    else: #this covers objects with no material
                        curve.materialName = 'DEFAULT' 
                                               
            blenderScene.objects.unlink(proxyObj)
            
    progress ("Writing materials to Pen Settings...")
    for materialName in drawing.settings.materialDefaults.keys():
        if  not materialName in drawing.settings.pens.keys() and not (materialName == "CUT" or materialName == "cut" or materialName == "Cut") and not (materialName == "VIGNETTE" or materialName == "vignette" or materialName == "Vignette"):
            drawing.settings.pens[materialName] = deepcopy(drawing.settings.pens['DEFAULT']) #for now...
            drawing.settings.pens[materialName].attributeDict["FILLCOLOR"] = drawing.settings.materialDefaults[materialName]
            drawing.settings.pens[materialName].name = materialName+"_pen"
            drawing.settings.penStorage.append(drawing.settings.pens[materialName])
            
def buildConnectivity(drawing):
    """
    builds topology for the mesh objects 
    """
    scene = drawing.scene
    settings = drawing.settings
    progress("Building connectivity for meshes...")
    if (settings.CULL_BACKFACES): progress("Culling back-facing triangles...")
    if (settings.AUTO_CREASE): progress ('Automatically detecting crease edges...')
    
    for mesh in scene.meshes:
        if ((mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE)):                     
            for face in mesh.faces:
                for (vert, nextVert) in zip(face.verts, face.verts[1:]+[face.verts[0]]):
                    hasEdge = False
                    for edge in vert.edges:
                        if nextVert in edge.verts:
                            if not edge in face.edges: 
                                face.edges.append(edge)
                            if not face in edge.faces: 
                                edge.faces.append(face)
                            hasEdge = True
                            
                    if not hasEdge:
                        raise("Error in mesh %s:Should have produced a corresponding edge! Please report this bug to Pantograph development team" % mesh.name)
                            
                if not len(face.edges)==3:
                    raise ("Wrong number of edges in mesh %s" % mesh.name)
                
            for curve in mesh.curves:
                for vert in curve.verts:
                    hasEdge = False
                    for edge in vert.edges:
                        if vert in edge.verts:
                            if not edge in curve.edges: curve.edges.append(edge)
                            hasEdge = True
                            
                    if not hasEdge:
                        raise("Error in mesh %s:Should have produced a corresponding edge! Please report this bug to Pantograph development team" % mesh.name)
                            
            #Handle back-face culling, if needed:
            if mesh.faces:
                if (settings.CULL_BACKFACES) and (mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE):
                    faceDestroyList = [face for face in mesh.faces if face.facing == BACK_FACING]
                            
                    [face.destroy() for face in faceDestroyList]
                    for face in faceDestroyList: del(face)
            
            #remove degenerate edges for non-curve meshes, or any edges left over from backface cull
            ### We should provide a possibility to have non-attached edges become curves!
                edgeDestroyList = []
                for edge in mesh.edges:
                    if len(edge.faces) == 0: #orphaned edge: just discard it
                        edgeDestroyList.append(edge)
                    
                    elif len(edge.faces)<2 or (edge.faces[0].facing != edge.faces[1].facing):
                        edge.SILHOUETTE = True
                        
                [edge.destroy() for edge in edgeDestroyList]
                for edge in edgeDestroyList: del(edge)
                
            #automatically find creases, if necessary
            if len(mesh.faces) > 0 and (settings.AUTO_CREASE) and (mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE):
                [doCrease(edge, mesh.creaseAngle) for edge in mesh.edges]
                
            
def makeFrustum(scene):
    """
    Creates a frustum cube for a frustum cull
    """
    progress("Creating the camera frustum...")
    #generate the necessary frustum points:
    if scene.projection == "persp":
        cameraLoc = scene.camera.resize3D()
        cameraViewVector = -Vector(0,0,1)
        cameraUpVector = -Vector(0,1,0)
        cameraRightVector = -Vector(1,0,0)
        
        nearCenter = cameraLoc + cameraViewVector * scene.near
        nearWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.near
        nearHeight = nearWidth / scene.aspect
        
        farCenter = (cameraLoc + cameraViewVector) * scene.far
        farWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.far
        farHeight = farWidth / scene.aspect
    
    else:
        cameraLoc = scene.camera.resize3D()
        cameraViewVector = -Vector(0,0,1)
        cameraUpVector = Vector(0,1,0)
        cameraRightVector = Vector(1,0,0)
        
        nearCenter = cameraLoc + cameraViewVector * scene.near
        nearWidth = float(scene.scale)
        nearHeight = float(scene.scale/scene.aspect)
        
        farCenter = (cameraLoc + cameraViewVector) * scene.far
        farWidth = nearWidth
        farHeight = nearHeight

    nearLeftTop = Vert(scene, ((nearCenter + (cameraUpVector * nearHeight/2) - (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
    nearRightTop = Vert(scene, ((nearCenter + (cameraUpVector * nearHeight/2) + (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
    nearRightBottom = Vert(scene, ((nearCenter - (cameraUpVector * nearHeight/2) + (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
    nearLeftBottom = Vert(scene, ((nearCenter - (cameraUpVector * nearHeight/2) - (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
    
    farLeftTop = Vert(scene, ((farCenter + (cameraUpVector * farHeight/2) - (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
    farRightTop = Vert(scene, ((farCenter + (cameraUpVector * farHeight/2) + (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
    farRightBottom = Vert(scene, ((farCenter - (cameraUpVector * farHeight/2) + (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
    farLeftBottom = Vert(scene, ((farCenter - (cameraUpVector * farHeight/2) - (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)

    debug(   'Camera Location:', cameraLoc)
    debug('   Camera frustum:')
    debug('         near_WxH:', nearHeight, nearWidth)
    debug('          far_WxH:', farHeight, farWidth)
    debug('      near_center:', nearCenter)
    debug('    near_top_left: ',nearLeftTop.loc)
    debug('   near_top_right: ',nearRightTop.loc)
    debug(' near_bottom_left: ',nearLeftBottom.loc)
    debug('near_bottom_right: ',nearRightBottom.loc)
    debug('     far_top_left: ',farLeftTop.loc)
    debug('    far_top_right: ',farRightTop.loc)
    debug('  far_bottom_left: ',farLeftBottom.loc)
    debug(' far_bottom_right: ',farRightBottom.loc)
    
    frustumMesh = Mesh(scene, "Frustum")
    frustumMesh.type = FRUSTUM_TYPE
    
    nearFace = Face(frustumMesh, [nearRightTop, nearLeftTop, nearLeftBottom, nearRightBottom])
    nearFace.type = FRUSTUM_TYPE
    farFace = Face(frustumMesh, [farLeftTop, farRightTop, farRightBottom, farLeftBottom])
    farFace.type = FRUSTUM_TYPE
    topFace = Face(frustumMesh, [nearLeftTop, nearRightTop, farRightTop, farLeftTop])
    topFace.type = FRUSTUM_TYPE
    bottomFace = Face(frustumMesh, [nearRightBottom, nearLeftBottom, farLeftBottom, farRightBottom])
    bottomFace.type = FRUSTUM_TYPE
    rightFace = Face(frustumMesh, [farRightTop, nearRightTop, nearRightBottom, farRightBottom])
    rightFace.type = FRUSTUM_TYPE
    leftFace = Face(frustumMesh, [nearLeftTop, farLeftTop, farLeftBottom, nearLeftBottom])
    leftFace.type = FRUSTUM_TYPE
        
    nearFace.facing = FRONT_FACING
    farFace.facing = BACK_FACING
    topFace.facing = BACK_FACING
    bottomFace.facing = BACK_FACING
    rightFace.facing = BACK_FACING
    leftFace.facing = BACK_FACING  
    
    [computeNormal(face) for face in frustumMesh.faces]
    if scene.projection == "ortho":[face.normal.negate() for face in frustumMesh.faces] #the ortho projection matrix is mirrored, producing a reversed cube
    
    #Remove faces outside the frustum
    progress("Frustum culling...") 
    
    #remove cutting faces from scene.faces
    for face in frustumMesh.faces:
        scene.faces.remove(face)
            
    #create bounding boxes for the cutting meshes
    myVerts = [nearLeftTop, nearRightTop, nearRightBottom, nearLeftBottom, farLeftTop, farRightTop, farRightBottom, farLeftBottom]
    frustumMeshBbox = BoundingBox(myVerts)
        
    #make a list of the faces that fall within the bounding box of the cutting mesh
    cutawayList = [face for face in scene.faces if (face.mesh.type == GEOMETRY_TYPE and frustumMeshBbox.doesIntersect(face.bbox))]
                    
    #split all the faces remaining in cutawayList
    for cuttingFace in frustumMesh.faces:
        myCutawayList = [] #faces that might be removed by the cutting mesh
        classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
        
        #classify all the faces according to the splitting face (setting classifyDict in the process)
        [classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
        
        #make a list of either classifications or splits
        splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
        
        for (classify, cutFace) in zip(splitList, cutawayList):
            #we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
            if classify == IN_FRONT:
                if cuttingFace.facing == BACK_FACING:
                    myCutawayList.append(cutFace)
                    
            elif classify == BEHIND:
                if  cuttingFace.facing == FRONT_FACING:
                    myCutawayList.append(cutFace)
                    
            elif classify == ON:
                myCutawayList.append(cutFace)
                    
            else:#we have an intersection
                if cuttingFace.facing == BACK_FACING:
                    myCutawayList.append(classify[0])
                    
                elif cuttingFace.facing == FRONT_FACING:
                    myCutawayList.append(classify[1])
                    
        cutawayList = copy(myCutawayList)
    keepSet = set(cutawayList)
            
    #we want to destroy all scene faces EXCEPT for the ones we have selected:
    destroySet = scene.faces.difference(keepSet)

    #destroy all cutaway faces
    progress("Number of faces removed outside of frustum: %i" % len(destroySet))

    for cutFace in destroySet:
        if cutFace.mesh.type == GEOMETRY_TYPE:
            for edge in cutFace.edges:
                edge.SILHOUETTE = False
                edge.CUT = False
                    
            cutFace.destroy()
            del(cutFace)
    
def doVignettes(scene):
    progress("Removing faces outside vignette...") 
    
    cuttingMeshList = []
    
    for mesh in scene.meshes:
        if mesh.type == VIGNETTE_TYPE: 
            cuttingMeshList.append(mesh)
    
    if cuttingMeshList:
        #remove cutting faces from scene.faces
        for mesh in cuttingMeshList:
            for face in mesh.faces:
                scene.faces.remove(face)
                
        #create bounding boxes for the cutting meshes
        cuttingMeshBboxList = []
        cuttingFaceVertList = []
        for mesh in cuttingMeshList:
            extend = cuttingFaceVertList.extend
            [extend(face.verts) for face in mesh.faces]
            cuttingMeshBboxList.append(BoundingBox(cuttingFaceVertList))
            
        #create a set to contain faces to be kept
        keepSet = set()
            
        #compare all other faces against cutting faces
        for (mesh, cuttingBbox) in zip(cuttingMeshList, cuttingMeshBboxList):
            #make a list of the faces that fall within the bounding box of the cutting mesh
            cutawayList = [face for face in scene.faces if (cuttingBbox.doesIntersect(face.bbox) and face.mesh.type == GEOMETRY_TYPE)]
                            
            #split all the faces remaining in cutawayList
            for cuttingFace in mesh.faces:
                myCutawayList = [] #faces that might be removed by the cutting mesh
                classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
                
                #classify all the faces according to the splitting face (setting classifyDict in the process)
                [classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
                
                #make a list of either classifications or splits
                splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
                
                for (classify, cutFace) in zip(splitList, cutawayList):
                    #we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
                    if classify == IN_FRONT:
                        if cuttingFace.facing == BACK_FACING:
                            myCutawayList.append(cutFace)
                            
                    elif classify == BEHIND:
                        if  cuttingFace.facing == FRONT_FACING:
                            myCutawayList.append(cutFace)
                            
                    elif classify == ON:
                        myCutawayList.append(cutFace)
                            
                    else:#we have an intersection
                        if cuttingFace.facing == BACK_FACING:
                            myCutawayList.append(classify[0])
                            
                        elif cuttingFace.facing == FRONT_FACING:
                            myCutawayList.append(classify[1])
                            
                cutawayList = copy(myCutawayList)
            keepSet = keepSet.union(set(cutawayList))
                
        #we want to destroy all scene faces EXCEPT for the ones we have selected:
        destroySet = scene.faces.difference(keepSet)

        #destroy all cutaway faces
        progress("Number of faces removed outside of vignettes: %i" % len(destroySet))

        for cutFace in destroySet:
            if cutFace.mesh.type == GEOMETRY_TYPE:
                for edge in cutFace.edges:
                    if not edge.CUTISVISIBLE:
                        edge.CUT = True
                    
                    else:
                        edge.SILHOUETTE = True
                            
                cutFace.destroy()
                del(cutFace)
            
def doCutaways(scene):
    progress("Removing cutaway faces...") 
    
    #make a dict of the cutting faces by mesh
    cuttingMeshList = []
    
    for mesh in scene.meshes:
        if mesh.type == CUTTING_TYPE: 
            cuttingMeshList.append(mesh)
            
    #remove cutting faces from scene.faces
    for mesh in cuttingMeshList:
        for face in mesh.faces:
            scene.faces.remove(face)
            
    #create bounding boxes for the cutting meshes
    cuttingMeshBboxList = []
    cuttingFaceVertList = []
    
    for mesh in cuttingMeshList:
        extend = cuttingFaceVertList.extend
        [extend(face.verts) for face in mesh.faces]
        cuttingMeshBboxList.append(BoundingBox(cuttingFaceVertList))
    #compare all other faces against cutting faces
    for (mesh, cuttingBbox) in zip(cuttingMeshList, cuttingMeshBboxList):
        #make a list of the faces that fall within the bounding box of the cutting mesh
        cutawayList = [face for face in scene.faces if (face.mesh.layers.intersection(mesh.layers) and cuttingBbox.doesIntersect(face.bbox))]
                        
        #split all the faces remaining in cutawayList
        for cuttingFace in mesh.faces:
            myCutawayList = [] #faces that might be removed by the cutting mesh
            classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
            
            #classify all the faces according to the splitting face (setting classifyDict in the process)
            [classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
            
            #make a list of either classifications or splits
            splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
            
            for (classify, cutFace) in zip(splitList, cutawayList):
                #we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
                if classify == IN_FRONT:
                    if cuttingFace.facing == BACK_FACING:
                        myCutawayList.append(cutFace)
                        
                elif classify == BEHIND:
                    if  cuttingFace.facing == FRONT_FACING:
                        myCutawayList.append(cutFace)
                        
                elif classify == ON:
                    myCutawayList.append(cutFace)
                        
                else:#we have an intersection
                    if cuttingFace.facing == BACK_FACING:
                        myCutawayList.append(classify[0])
                        
                    elif cuttingFace.facing == FRONT_FACING:
                        myCutawayList.append(classify[1])
                        
            cutawayList = copy(myCutawayList)

        #destroy all cutaway faces
        progress("Number of faces removed in cut-away: %i" % len(cutawayList))

        for cutFace in cutawayList:
            for edge in cutFace.edges:
                if edge.CUTISVISIBLE:
                    edge.CUT = True
                
                else:
                    edge.SILHOUETTE = True
                    
            cutFace.destroy()
            del(cutFace)
        
def occludeAndCull(drawing):    
    scene = drawing.scene
    
    zTopList=[]
    zCurrentList=[]
    
    frontOccludingRegion = Region(scene)
    visiblePolygonList = []
    hiddenPolygonList = []
    face2PolygonDict = dict()
    
    progress("Sorting faces by depth and culling occluded faces...")
    
    #generate window coords for all verts
    [clip2Window(vert, drawing) for vert in scene.verts]
        
    #sort faces by the z-value closest to the camera (zMin)
    [insort(zTopList,(face.bbox.zMin,face)) for face in scene.faces]
            
    #Look at each face in zTopList:
    #make occluding regions (in 2D coords for each face)
    for pair in zTopList:
        face = pair[1]
        face2PolygonDict[face] = Region(scene,vertList = face.verts)
        
    #check if any of the current faces can be popped off, and add them to the sorted list
        removalList = []
        
        for currentFace in zCurrentList:
            if currentFace.bbox.zMax < face.bbox.zMin: #currentFace is entirely in front of the face
                if drawing.settings.pens[currentFace.materialName].attributeDict["FILLCOLOR"].a == 1: #transparent faces do not occlude
                    frontOccludingRegion = frontOccludingRegion + face2PolygonDict[currentFace]
                    
                visiblePolygonList.append(currentFace)
                removalList.append(currentFace)
                
        for currentFace in removalList:
            zCurrentList.remove(currentFace)
                    
    #check if our face is occluded by the current region
        if frontOccludingRegion.covers(face2PolygonDict[face]):
            hiddenPolygonList.append(face)
            
        #if not occluded, put the polygon in the current list
        else:
            zCurrentList.append(face)
                 
    debug('mark 3')
    
    for face in zCurrentList:
        visiblePolygonList.append(face)#put anything left in zCurrentList on the end of visiblePolygonList
    
    progress("Number of faces removed before BSP sort: %i" % len(hiddenPolygonList))
    progress("Number of faces going into BSP tree: %i" % len(visiblePolygonList))
    
    return (visiblePolygonList, hiddenPolygonList)               
                       
def BSPSort(scene, visibleFaces):
    """
    Sorts the scene's polygons into a BSP Tree, using
    the Least-Crossed method to chose new node
    """ 
    Sorting = Profiler('Sorting')
    Sorting.start()
    
    faceList = visibleFaces
    
    curveList = []
    for mesh in scene.meshes:
        if mesh.curves:
            curveList.append(mesh.curves[0])            
        
    drawingQueue = []
                
    #sort the faces in a BSP tree
    TOTAL_FACES = len(faceList)
    
    progress('Sorting meshes into BSP tree...')
    myBSPTree = BSPTree(faceList, curveList)
    
    drawingQueue = myBSPTree.backToFront(0)
    
    progress(Sorting.end())
    
    global CLASSIFY_CALLS
    global SPLIT_CALLS
    global MAX_DEPTH
    print("Maximum tree depth is %i" % MAX_DEPTH)
    print("CLASSIFY calls: %i" % CLASSIFY_CALLS)
    print("SPLIT calls: %i" % SPLIT_CALLS)
    print( "Faces after splitting: %i" % len(drawingQueue) )
    
    return drawingQueue

def clipAndCombine(drawing):
    """
    Sorts faces and edges into dicts by materialName.  Transparent faces are sorted into a queue to allow preservation of layers.
    Returns a tuple of (dict of fills, dict of polylines, queue of transparent fills)
    """
    Clipping = Profiler('2D clipping')
    Clipping.start()
    
    scene = drawing.scene
    
    #make sure all verts have a 2d location
    [clip2Window(vert, drawing) for vert in drawing.scene.verts]
    
    drawing.drawingQueue.reverse()#for painter's algorithm
    
    #these regions are used for masking and clipping
    drawing.lineClippingRegion = Region(scene)
    if drawing.settings.USE_ALPHA:drawing.fillClippingRegion = Region(scene)    
    
    drawing.fillDict = dict()
    
    drawing.transparentFillDict = dict()        
    drawing.transparentFillQueue = list()
    drawing.currentTransparentRegion = list()
    
    drawing.material2PolylinesDict = dict()
        
    #initialize the dicts for storing polylines and fills
    progress('Initializing polyline and fill dicts...')
    for object in (drawing.drawingQueue + drawing.hiddenPolygons):
        if not object.materialName in drawing.fillDict.keys():
            drawing.fillDict[object.materialName] = dict()
            drawing.transparentFillDict[object.materialName] = dict()
            
        if not object.mesh in drawing.fillDict[object.materialName].keys():
            drawing.fillDict[object.materialName][object.mesh] = Region(scene)
            drawing.transparentFillDict[object.materialName][object.mesh] = Region(scene)
            
        if not object.materialName in drawing.material2PolylinesDict.keys():
            drawing.material2PolylinesDict[object.materialName] = dict()
            
        if not object.mesh in drawing.material2PolylinesDict[object.materialName].keys():
            drawing.material2PolylinesDict[object.materialName][object.mesh] = dict()
            
        myPolylineDict = drawing.material2PolylinesDict[object.materialName][object.mesh]
            
        #initialize the material's polylinedict, if necessary
        if not 'SILHOUETTE' in myPolylineDict.keys():
            myPolylineDict['SILHOUETTE'] = []
            
        if not 'CREASE' in myPolylineDict.keys():
            myPolylineDict['CREASE'] = []
            
        if not 'CUT' in myPolylineDict.keys():
            myPolylineDict['CUT'] = []
            
        if not 'MESH' in myPolylineDict.keys():
            myPolylineDict['MESH'] = []
            
        if not 'HIDDEN' in myPolylineDict.keys():
            myPolylineDict['HIDDEN'] = []
            
        if not 'CURVE' in myPolylineDict.keys():
            myPolylineDict['CURVE'] = []
            
    #make a Region from each face, grouping by material and then by mesh
    progress('Classifying faces by material and by mesh...')
    [clipObjects(drawing, object, drawing.material2PolylinesDict[object.materialName][object.mesh], True) for object in drawing.hiddenPolygons]
    [clipObjects(drawing, object, drawing.material2PolylinesDict[object.materialName][object.mesh], False) for object in drawing.drawingQueue]
    
    #combine line segments
    progress('Combining segments into polylines...')
    [combinePolylines(drawing, polylineDict) for material in drawing.material2PolylinesDict.keys() \
    for mesh in drawing.material2PolylinesDict[material].keys() \
    for polylineDict in drawing.material2PolylinesDict[material].values() ]
                                    
    if (not drawing.currentTransparentRegion in drawing.transparentFillQueue) and drawing.currentTransparentRegion: drawing.transparentFillQueue.append(drawing.currentTransparentRegion)
    drawing.transparentFillQueue.reverse()
    drawing.globalSilhouetteRegion = drawing.lineClippingRegion
    
    progress(Clipping.end())
#---CAIRO-RELATED CLASSES---#000000#FFFFFF--------------------------------------
class RGBA:
    def __init__(self,r=1.0,g=1.0,b=1.0,a=1.0):
        self.r=r
        self.g=g
        self.b=b
        self.a=a
            
    def __getitem__(self):
        return
        
class Pen(RGBA):
    BLACK=RGBA(0,0,0,1)
    WHITE=RGBA(1,1,1,1)
    GREYTRANSPARENT=RGBA(0.5,0.5,0.5,0.5)
    GREYOPAQUE=RGBA(0.5,0.5,0.5,1)
    DASH=(5,5)
    DOTDASH=(4,4,10,4)
    DOT=(1,4)
    SOLID=(1,0)
    MITER = cairo.LINE_JOIN_MITER
    FILLET = cairo.LINE_JOIN_ROUND
    BEVEL = cairo.LINE_JOIN_BEVEL
    BUTT = cairo.LINE_CAP_BUTT
    ROUND = cairo.LINE_CAP_ROUND
    SQUARE = cairo.LINE_CAP_SQUARE
    lineTypeList = ["SILHOUETTE", "CREASE", "MESH", "HIDDEN", "CUT", "CURVE", "MATERIAL SILHOUETTE"]

    def __init__(self,  attributeDict =
                            {"SILHOUETTE":  {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.75, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "CREASE":       {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "MESH":         {"Visible":True, "lineColor":RGBA(0.5,0.5,0.5,1), "lineWidth":0.25, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "HIDDEN":       {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(5,5), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "CUT":          {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":2.0, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "CURVE":        {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(4,4,10,4), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "MATERIAL SILHOUETTE":     {"Visible":False, "lineColor":BLACK, "lineWidth":0.25, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
                            "FILLCOLOR":    RGBA(0.5,0.5,0.5,1),
                            "FILLRASTER":   {"toggle":False, "filename":""}},
                name = 'DEFAULT_pen'):
        
        self.attributeDict = attributeDict
        self.name = name
                        
class Drawing:    
    def __init__(self):
        self.scene = Scene()
        self.rendered = False
        
        self.drawingQueue = []
        self.hiddenPolygons = []
        self.visiblePolygons = []
        
        self.fillDict = False
        self.hiddenPolylinesDict = False
        self.transparentFillDict = False
        
        self.globalSilhouetteRegion = Region(self.scene)
        
        self.rasterFiles = dict()
        
        self.settings = Settings()
        self.dashTypes = {'DASH':Pen.DASH, 'DOTDASH':Pen.DOTDASH, 'DOT':Pen.DOT, 'SOLID':Pen.SOLID}
        self.dashTypesList = self.dashTypes.keys()
        self.dashTypesList.sort()
        self.endStyles = {'BUTT':Pen.BUTT, 'ROUND':Pen.ROUND, 'SQUARE':Pen.SQUARE}
        self.endStylesList = self.endStyles.keys()
        self.endStylesList.sort()
        self.joinStyles = {'MITER':Pen.MITER, 'FILLET':Pen.FILLET, 'BEVEL':Pen.BEVEL}
        self.joinStylesList = self.joinStyles.keys()
        self.joinStylesList.sort()
        
        self.progressMaker = None #This gets set by the GUI
                                        
    def render(self, width = 0, height = 0, filename = 'temp.svg', canvas = 'screen'):
        """
        This method handles the actual rendering of the Drawing
        """  
        if canvas == 'screen':
            drawable = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width), int(height))
            context = cairo.Context(drawable)
            context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
                    
        elif canvas == 'svg':
            file = open(filename, mode = 'w')
            drawable = cairo.SVGSurface(filename, width/1.25, height/1.25)
            context = cairo.Context(drawable)
            context.scale(0.8,0.8) #this corrects the mysterious Cairo 125% scaling!
                                    
        elif canvas == 'pdf':
            file = open(filename, mode = 'w')
            drawable = cairo.PDFSurface(filename, width, height)
            context = cairo.Context(drawable)
            
        elif canvas == 'png':
            drawable = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width), int(height))
            context = cairo.Context(drawable)
            context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
                    
        #Make a screen-sized rectangle and use it to clip the whole drawing
        clipRectangle = context.new_path()
        context.move_to(0,0)
        context.line_to(self.WIDTH,0)
        context.line_to(self.WIDTH,self.HEIGHT)
        context.line_to(0,self.HEIGHT)
        context.close_path()
        
        context.clip()
                        
        #Make another screen-sized rectangle and fill it with a gradient matching the Blender horizon and zenith colors for the background
        background = cairo.SurfacePattern(context.get_target())
        gradient = cairo.LinearGradient(0,0,0,self.HEIGHT)        
        cairo.LinearGradient.add_color_stop_rgb(gradient, 0, self.zenithColor.r, self.zenithColor.g, self.zenithColor.b)
        cairo.LinearGradient.add_color_stop_rgb(gradient, 1, self.horizonColor.r, self.horizonColor.g, self.horizonColor.b)
        context.set_source(gradient)
                                                        
        context.new_path()
        context.move_to(0,0)
        context.line_to(0, self.HEIGHT)
        context.line_to(self.WIDTH, self.HEIGHT)
        context.line_to(self.WIDTH, 0)
        context.close_path()
        context.fill()
        
        #first, draw the opaque fills:        
        for material in self.fillDict.keys():
            for mesh in self.fillDict[material].keys():
                myPen = self.settings.pens[material]
                
                if myPen.attributeDict["FILLRASTER"]["toggle"] and myPen.attributeDict["FILLRASTER"]["filename"]:
                    #check to make sure the file is loaded
                    if not myPen.attributeDict["FILLRASTER"]["filename"] in self.rasterFiles.keys():
                        myRasterSurface =  cairo.ImageSurface.create_from_png(myPen.attributeDict["FILLRASTER"]["filename"])
                        self.rasterFiles[myPen.attributeDict["FILLRASTER"]["filename"]] = myRasterSurface
                     
                    myRasterSurface = self.rasterFiles[myPen.attributeDict["FILLRASTER"]["filename"]]  
                    context.set_source_surface(myRasterSurface)
                    
                else:
                    context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
                                            myPen.attributeDict["FILLCOLOR"].g,
                                            myPen.attributeDict["FILLCOLOR"].b,
                                            myPen.attributeDict["FILLCOLOR"].a)
                                            
                context.set_line_width(0)
                context.new_path()
                self.fillDict[material][mesh].draw(context)
                context.fill()
                
        #second, draw the transparent fills in queue order:
        for pair in self.transparentFillQueue:
            myPen = self.settings.pens[pair[0]]
            
            context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
                                    myPen.attributeDict["FILLCOLOR"].g,
                                    myPen.attributeDict["FILLCOLOR"].b,
                                    myPen.attributeDict["FILLCOLOR"].a)
                                    
            context.set_line_width(0) 
            
            context.new_path()
            pair[1].draw(context)
            context.fill()
            
        #draw the material silhouettes:
        for material in self.fillDict.keys():
            for mesh in self.fillDict[material].keys():
                myPen = self.settings.pens[material]
                
                if myPen.attributeDict["MATERIAL SILHOUETTE"]["Visible"]:
                    context.set_line_join(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineCap"])
                    context.set_source_rgba(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].r,
                                            myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].g,
                                            myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].b,
                                            myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].a)
                                                                                    
                    context.set_line_width(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineDash"])
                    
                    context.new_path()
                    self.fillDict[material][mesh].outline(context)
                    context.stroke()
                
        for material in self.transparentFillDict.keys():
            for mesh in self.transparentFillDict[material].keys():
                myPen = self.settings.pens[material]
                
                context.set_line_join(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineJoin"])
                context.set_line_cap(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineCap"])
                context.set_source_rgba(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].r,
                                        myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].g,
                                        myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].b,
                                        myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].a)
                                                                                
                context.set_line_width(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"])
                context.set_dash(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineDash"])
                        
                if len(self.transparentFillDict[material][mesh].polygon): 
                    context.new_path()
                    self.transparentFillDict[material][mesh].outline(context)
                    context.stroke()
        
        #draw the polylines                  
        for material in self.material2PolylinesDict.keys():
            for mesh in self.material2PolylinesDict[material].keys():
                myPen = self.settings.pens[material]
                myPolylinesDict = self.material2PolylinesDict[material][mesh]
                            
                #next, draw the mesh edges:
                if myPen.attributeDict["MESH"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["MESH"]["lineColor"].r,
                                            myPen.attributeDict["MESH"]["lineColor"].g,
                                            myPen.attributeDict["MESH"]["lineColor"].b,
                                            myPen.attributeDict["MESH"]["lineColor"].a)
                                            
                    context.set_line_join(myPen.attributeDict["MESH"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["MESH"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["MESH"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["MESH"]["lineDash"])
                    
                    context.new_path()
                    for polyline in myPolylinesDict['MESH']:
                        polyline.draw(context)
                    context.stroke()
                        
                #next, draw the sharp(crease) edges:
                if myPen.attributeDict["CREASE"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["CREASE"]["lineColor"].r,
                                            myPen.attributeDict["CREASE"]["lineColor"].g,
                                            myPen.attributeDict["CREASE"]["lineColor"].b,
                                            myPen.attributeDict["CREASE"]["lineColor"].a)
                                            
                    context.set_line_join(myPen.attributeDict["CREASE"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["CREASE"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["CREASE"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["CREASE"]["lineDash"])
                    
                    context.new_path()
                    for polyline in myPolylinesDict['CREASE']:
                        polyline.draw(context)
                    context.stroke()
                                                
                #next, draw the silhouette edges:
                if myPen.attributeDict["SILHOUETTE"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["SILHOUETTE"]["lineColor"].r,
                                            myPen.attributeDict["SILHOUETTE"]["lineColor"].g,
                                            myPen.attributeDict["SILHOUETTE"]["lineColor"].b,
                                            myPen.attributeDict["SILHOUETTE"]["lineColor"].a)
                                           
                    context.set_line_join(myPen.attributeDict["SILHOUETTE"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["SILHOUETTE"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["SILHOUETTE"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["SILHOUETTE"]["lineDash"])
                    
                    context.new_path()
                    for polyline in myPolylinesDict['SILHOUETTE']:
                        polyline.draw(context)
                    context.stroke()
                    
                #next, draw the cut edges:
                if myPen.attributeDict["CUT"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["CUT"]["lineColor"].r,
                                            myPen.attributeDict["CUT"]["lineColor"].g,
                                            myPen.attributeDict["CUT"]["lineColor"].b,
                                            myPen.attributeDict["CUT"]["lineColor"].a)
                                            
                    context.set_line_join(myPen.attributeDict["CUT"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["CUT"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["CUT"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["CUT"]["lineDash"])
                    
                    context.new_path()
                    for polyline in myPolylinesDict['CUT']:
                        polyline.draw(context)
                    context.stroke()
                            
                #next, draw the hidden lines  
                if myPen.attributeDict["HIDDEN"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["HIDDEN"]["lineColor"].r,
                                            myPen.attributeDict["HIDDEN"]["lineColor"].g,
                                            myPen.attributeDict["HIDDEN"]["lineColor"].b,
                                            myPen.attributeDict["HIDDEN"]["lineColor"].a)
                                            
                    context.set_line_join(myPen.attributeDict["HIDDEN"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["HIDDEN"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["HIDDEN"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["HIDDEN"]["lineDash"])
                    
                    context.new_path()
                    for polyline in myPolylinesDict['HIDDEN']:
                        polyline.draw(context)
                    context.stroke()
                        
                #next, draw the curve lines  
                if myPen.attributeDict["CURVE"]["Visible"]:
                    context.set_source_rgba(myPen.attributeDict["CURVE"]["lineColor"].r,
                                            myPen.attributeDict["CURVE"]["lineColor"].g,
                                            myPen.attributeDict["CURVE"]["lineColor"].b,
                                            myPen.attributeDict["CURVE"]["lineColor"].a)
                                            
                    context.set_line_join(myPen.attributeDict["CURVE"]["lineJoin"])
                    context.set_line_cap(myPen.attributeDict["CURVE"]["lineCap"])
                    context.set_line_width(myPen.attributeDict["CURVE"]["lineWidth"])
                    context.set_dash(myPen.attributeDict["CURVE"]["lineDash"])  
                                      
                    context.new_path()
                    for polyline in myPolylinesDict['CURVE']:
                        if polyline.verts:
                            polyline.draw(context)
                    context.stroke()
                        
        #draw the global silhouette
        if self.settings.globalLineVisible and self.scene.faces:
            context.set_line_join(self.settings.globalLineJoin)
            context.set_line_cap(self.settings.globalLineCap)
            context.set_source_rgba(self.settings.globalLineColor.r,
                                    self.settings.globalLineColor.g,
                                    self.settings.globalLineColor.b,
                                    self.settings.globalLineColor.a)
                                                                            
            context.set_line_width(self.settings.globalLineWidth)
            context.set_dash(self.settings.globalLineDash)
                    
            context.new_path()
            self.globalSilhouetteRegion.outline(context)
            context.stroke()
        
                                                
        if canvas == 'svg' or canvas == 'pdf':
            file.close()
            
        elif canvas == 'png':
            file = open(filename, mode = 'w')
            drawable.write_to_png(filename)
            file.close
            
        else:
            return drawable
            
    def renderMovie(self, movie):
        """
        This method handles rendering a number of frames to an swf movie
        """     
        #initialize the shape queue, if necessary
        shapeQueue = list()
        
        if self.shapes:
            shape = (shape for shape in self.shapes)
        
        else:
            shape = repeat(ming.SWFShape())
            
        #draw the screen background:
        gradient = ming.SWFGradient()
        gradient.addEntry(0.0, self.horizonColor.r * 255, self.horizonColor.g * 255, self.horizonColor.b * 255)
        gradient.addEntry(self.HEIGHT/self.WIDTH, self.zenithColor.r * 255, self.zenithColor.g * 255, self.zenithColor.b * 255)
        background = ming.SWFShape()
        background.setLine(0,0,0,0,0)
        gradientFill = background.addFill(gradient, ming.SWFFILL_LINEAR_GRADIENT)
        gradientFill.rotateTo(90)
        background.setRightFill(gradientFill)

        background.drawLineTo(0,0)
        background.drawLineTo(0,self.HEIGHT)
        background.drawLineTo(self.WIDTH,self.HEIGHT)
        background.drawLineTo(self.WIDTH,0)
            
        movie.add(background)
        
        #first, draw the opaque fills:        
        for material in self.fillDict.keys():
            myShape = shape.next()
            myPen = self.settings.pens[material]
            for mesh in self.fillDict[material].keys():
                self.fillDict[material][mesh].drawSWF(myShape, movie,myPen.attributeDict["FILLCOLOR"])
                
            movie.add(myShape)
                
        #second, draw the transparent fills in queue order:
        for pair in self.transparentFillQueue:
            myShape = shape.next()
            movie.remove(myShape)
            myPen = self.settings.pens[pair[0]]
            pair[1].drawSWF(myShape, movie, myPen.attributeDict["FILLCOLOR"])
                
            movie.add(myShape)
            
        #draw the material silhouettes:
        for material in self.fillDict.keys():
            myPen = self.settings.pens[material]
            if myPen.materialLineVisible:
                myShape = shape.next()
                movie.remove(myShape)
                for mesh in self.fillDict[material].keys():
                    self.fillDict[material][mesh].outlineSWF(myShape, movie, myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"], myPen.attributeDict["fillColor"])
                    
                movie.add(myShape)
                
                
        for material in self.transparentFillDict.keys():
            myPen = self.settings.pens[material]
            if myPen.materialLineVisible:
                myShape = shape.next()
                movie.remove(myShape)
                for mesh in self.transparentFillDict[material].keys():
                    self.transparentFillDict[material][mesh].outlineSWF(myShape, movie, myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"], myPen.attributeDict["fillColor"])
                    
                movie.add(myShape)
        
        #draw the polylines                  
        for material in self.material2PolylinesDict.keys():
            myPen = self.settings.pens[material]
            for mesh in self.material2PolylinesDict[material].keys():
                myPolylinesDict = self.material2PolylinesDict[material][mesh]
                            
                #next, draw the mesh edges:
                if myPen.meshVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['MESH']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["MESH"]["lineWidth"], myPen.attributeDict["MESH"]["lineColor"])
                        
                    movie.add(myShape)
                        
                #next, draw the sharp(crease) edges:
                if myPen.creaseVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['CREASE']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["CREASE"]["lineWidth"], myPen.attributeDict["CREASE"]["lineColor"])
                        
                    movie.add(myShape)
                                                
                #next, draw the silhouette edges:
                if myPen.silhouetteVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['SILHOUETTE']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["SILHOUETTE"]["lineWidth"], myPen.attributeDict["SILHOUETTE"]["lineColor"])
                        
                    movie.add(myShape)
                        
                #next, draw the cut edges:
                if myPen.cutVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['CUT']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["CUT"]["lineWidth"], myPen.attributeDict["CUT"]["lineColor"])
                        
                    movie.add(myShape)
                            
                #next, draw the hidden lines  
                if myPen.hiddenVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['HIDDEN']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["HIDDEN"]["lineWidth"], myPen.attributeDict["HIDDEN"]["lineColor"])
                        
                    movie.add(myShape)
                        
                #next, draw the curve lines  
                if myPen.curveVisible:
                    myShape = shape.next()
                    movie.remove(myShape)
                    for polyline in myPolylinesDict['CURVE']:
                        polyline.drawSWF(myShape, movie, myPen.attributeDict["CURVE"]["lineWidth"], myPen.attributeDict["CURVE"]["lineColor"])
                        
                    movie.add(myShape)
                        
        #draw the global silhouette
        if self.settings.globalLineVisible and self.scene.faces:
            myShape = shape.next()
            movie.remove(myShape)
            self.globalSilhouetteRegion.outlineSWF(myShape, movie, self.settings.globalLineWidth, self.settings.globalLineColor)
            
            movie.add(myShape)

    def renderSwatch(self, pen = Pen(), backgroundColor = (0.9,0.9,0.9,1), width = 50, height = 15):
        """
        Returns a dict of swatches for each linetype/fill of the current pen
        """
        attributeList = ["SILHOUETTE", "CREASE", "MESH", "HIDDEN", "CUT", "CURVE"]
        factor = 1
          
        swatchDict = dict()
                
        swatchFill = [(0,0), (0,height), (width, height), (width,0) ]
        swatchLine = [(0,height/2), (width, height/2)]
        
        myPen = pen
        
        #first, draw the line swatches:
        for attribute in attributeList:
            swatchDict[attribute] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
            context = cairo.Context(swatchDict[attribute])
            
            context.set_source_rgba(backgroundColor[0],
                                    backgroundColor[1],
                                    backgroundColor[2],
                                    backgroundColor[3])
                                    
            context.new_path()
            context.move_to(swatchFill[0][0], swatchFill[0][1])
            context.line_to(swatchFill[1][0], swatchFill[1][1])
            context.line_to(swatchFill[2][0], swatchFill[2][1])
            context.line_to(swatchFill[3][0], swatchFill[3][1])
            context.close_path()
            context.fill()

            context.set_line_join(myPen.attributeDict[attribute]["lineJoin"])
            context.set_line_cap(myPen.attributeDict[attribute]["lineCap"])
            context.set_source_rgba(myPen.attributeDict[attribute]["lineColor"].r,
                                    myPen.attributeDict[attribute]["lineColor"].g,
                                    myPen.attributeDict[attribute]["lineColor"].b,
                                    myPen.attributeDict[attribute]["lineColor"].a)
                                    
            context.set_line_width(myPen.attributeDict[attribute]["lineWidth"])
            context.set_dash(myPen.attributeDict[attribute]["lineDash"])
            
            context.new_path()
            context.move_to(swatchLine[0][0], swatchLine[0][1])
            context.line_to(swatchLine[1][0], swatchLine[1][1])

            context.stroke()

        
        #draw the material silhouette:                   
        swatchDict["MATERIAL SILHOUETTE"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
        context = cairo.Context(swatchDict["MATERIAL SILHOUETTE"])
        
        context.set_source_rgba(backgroundColor[0],
                                backgroundColor[1],
                                backgroundColor[2],
                                backgroundColor[3])
                                
        context.new_path()
        context.move_to(swatchFill[0][0], swatchFill[0][1])
        context.line_to(swatchFill[1][0], swatchFill[1][1])
        context.line_to(swatchFill[2][0], swatchFill[2][1])
        context.line_to(swatchFill[3][0], swatchFill[3][1])
        context.close_path()
        context.fill() 

                   
        context.set_line_join(myPen.attributeDict[attribute]["lineJoin"])
        context.set_line_cap(myPen.attributeDict[attribute]["lineCap"])
        context.set_source_rgba(myPen.attributeDict[attribute]["lineColor"].r,
                                myPen.attributeDict[attribute]["lineColor"].g,
                                myPen.attributeDict[attribute]["lineColor"].b,
                                myPen.attributeDict[attribute]["lineColor"].a)
                                
        context.set_line_width(myPen.attributeDict[attribute]["lineWidth"])
        context.set_dash(myPen.attributeDict[attribute]["lineDash"])
        
        context.new_path()
        context.move_to(swatchFill[0][0], swatchFill[0][1])
        context.line_to(swatchFill[1][0], swatchFill[1][1])
        context.line_to(swatchFill[2][0], swatchFill[2][1])
        context.line_to(swatchFill[3][0], swatchFill[3][1])
        context.close_path()
        context.stroke()
            
        #draw the global silhouette:
        swatchDict["GLOBAL SILHOUETTE"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
        context = cairo.Context(swatchDict["GLOBAL SILHOUETTE"])
        
        context.set_source_rgba(backgroundColor[0],
                                backgroundColor[1],
                                backgroundColor[2],
                                backgroundColor[3])
                                
        context.new_path()
        context.move_to(swatchFill[0][0], swatchFill[0][1])
        context.line_to(swatchFill[1][0], swatchFill[1][1])
        context.line_to(swatchFill[2][0], swatchFill[2][1])
        context.line_to(swatchFill[3][0], swatchFill[3][1])
        context.close_path()
        context.fill() 
                   
        context.set_line_join(self.settings.globalLineJoin)
        context.set_line_cap(self.settings.globalLineCap)
        context.set_source_rgba(self.settings.globalLineColor.r,
                                self.settings.globalLineColor.g,
                                self.settings.globalLineColor.b,
                                self.settings.globalLineColor.a)
                                                                        
        context.set_line_width(self.settings.globalLineWidth)
        context.set_dash(self.settings.globalLineDash)
                
        context.new_path()
        context.move_to(swatchFill[0][0], swatchFill[0][1])
        context.line_to(swatchFill[1][0], swatchFill[1][1])
        context.line_to(swatchFill[2][0], swatchFill[2][1])
        context.line_to(swatchFill[3][0], swatchFill[3][1])
        context.close_path()
        context.stroke()

            
        #finally, draw the fill
        swatchDict["FILL"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
        context = cairo.Context(swatchDict["FILL"])

        context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
                                myPen.attributeDict["FILLCOLOR"].g,
                                myPen.attributeDict["FILLCOLOR"].b,
                                myPen.attributeDict["FILLCOLOR"].a)
                        
        context.new_path()
        context.move_to(swatchFill[0][0], swatchFill[0][1])
        context.line_to(swatchFill[1][0], swatchFill[1][1])
        context.line_to(swatchFill[2][0], swatchFill[2][1])
        context.line_to(swatchFill[3][0], swatchFill[3][1])
        context.close_path()
        context.fill()
        
        return swatchDict
            
class Settings:
    """
    This is a container class for what is essentially a dictionary of
    material name -> pen
    """
    def __init__(self):
        self.pens = {} #this is a dict assigning pens to materials
        self.materialDefaults = {'DEFAULT':Pen().attributeDict["FILLCOLOR"]}
        self.pens['DEFAULT'] = Pen()
        self.penStorage = [Pen()]
        
        #Create a debug material for faulty faces
        if DEBUG:
            self.pens['DEBUG'] = Pen()
            self.pens['DEBUG'].attributeDict["FILLCOLOR"] = RGBA(1.0,0.0,0.0,1.0)
            self.penStorage.append(self.pens['DEBUG'])
        
        #These are the default global silhouette settings (is this the best place??)
        self.globalLineVisible = True
        self.globalLineWidth = 1.0
        self.globalLineDash = Pen.SOLID
        self.globalLineColor = Pen.BLACK
        
        #these are not user-settable at this time!
        self.globalLineJoin = Pen.BEVEL
        self.globalLineCap = Pen.ROUND
        
        #default file locations - these get set when the program first opens
        self.currentSettingsFolder = HOMEDIR
        self.currentExportFolder = HOMEDIR
        self.currentPen = self.pens['DEFAULT']  #This gets set by the GUI
        
        #these get set in the intro dialogue
        self.CULL_BACKFACES = CULL_BACKFACES
        self.USE_ALPHA = USE_ALPHA
        self.RENDER_ANIMATION = RENDER_ANIMATION
        self.AUTO_CREASE = AUTO_CREASE
        self.LOAD_PREVIOUS_SETTINGS = LOAD_PREVIOUS_SETTINGS
        
    def loadDefaultSettings( self ):
        try:
            self.load( HOMEDIR + "default.pog" )
        
        except:
            self.currentSettingsFolder = HOMEDIR
            self.currentExportFolder = HOMEDIR
            
    def saveDefaultSettings( self ):
        self.save( HOMEDIR + "default.pog" )
            
    def setMaterialToPen(materialName,pen):
        self.pens[materialName] = pen
        
    def setMaterialToDefault(materialName):
        self.pens[materialName] = Pen()
        self.pens[materialName].fillColor = self.materialDefaults[materialName]
        
    def setFillColorDefault(materialName):
        self.pens[materialName].fillColor = self.materialDefaults[materialName]
        
    def load(self,filename):
        myFile = open(filename, mode = 'r')
        self.materialDefaults, self.pens, self.penStorage, self.globalLineVisible, self.globalLineWidth, self.globalLineDash,\
        self.globalLineColor, self.globalLineCap, self.globalLineJoin = cPickle.load(myFile)
        myFile.close()
        
    def save(self,filename):
        myFile = open(filename, mode = 'w')
        cPickle.dump((self.materialDefaults, self.pens, self.penStorage, self.globalLineVisible, self.globalLineWidth, self.globalLineDash,\
        self.globalLineColor, self.globalLineCap, self.globalLineJoin),myFile)
        myFile.close()
                
    def loadPen(self,filename):
        myFile = open(filename, mode = 'r')
        myPen = Pen()
        (myPen.attributeDict, myPen.name) = cPickle.load(myFile)
        myPen.name = os.path.basename(filename).split('.')[0]
        
        for pen in self.penStorage:
            if myPen.name == pen.name:
                pen.attributeDict = myPen.attributeDict
                myFile.close()
                return 
        
        self.penStorage.append(myPen)

        myFile.close()
        
    def savePen(self,filename,currentPen):
        myPen = currentPen
        
        myFile = open(filename, mode = 'w')
        cPickle.dump(  (myPen.attributeDict, myPen.name),myFile)
        myFile.close()