# ----------------------------------------------------------------------------- # 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 # # ----------------------------------------------------------------------------- # Contributors: # 0.6 - Migius substantially cleaned up/speeded up point classification and face clipping and made some suggestions # about general global speedups. import Blender from Blender import Mesh, Object, Scene, Material from Blender.Mathutils import CrossVecs, Vector, Matrix, AngleBetweenVecs, MidpointVecs, DotVecs, VecMultMat from pantographConfig import * #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 MING = True except: print('Ming library not found - you will not be able to render animations') MING = False try: import cairo except: print('Cairo library not found - you will not be able export to SVG, PDF or PNG formats') CAIRO = False from copy import deepcopy, copy from bisect import insort from math import tan,atan,pi,degrees,sqrt,radians from decimal import Decimal from time import clock import cPickle import sys, os, gc, types 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---#AA00AA#FFFFFF------------------------------------------- #These are all record-keeping variables CLASSIFY_CALLS = 0 SPLIT_CALLS = 0 MAX_DEPTH = 0 PROCESSED_FACES = 0 TOTAL_FACES = 0 CLIP_COUNTER = 0 #Precision############# setTolerance(EPSILON2D) #this controls the tolerance of the Polygon package RECURSION_LIMIT = 100 #this is set to prevent the BSP algorithm from over-recursing (but is not hooked up yet) #Spacial Classification############## NOT_SET = None IN_FRONT = 0001 ON = 0100 BEHIND = 0010 INTERSECTING = 0011 #FACE BIT CODES: FRONT_FACING = 00000001 #Face normal faces towards the camera = 1 BACK_FACING = 00000010 #Face normal faces away from camera = 2 GEOMETRY_TYPE = 00000100 CUTTING_TYPE = 00001000 PARTITION_TYPE =00010000 FRUSTUM_TYPE = 00100000 VIGNETTE_TYPE = 01000000 #EDGE BIT CODES: CREASE = 000000001 #Determines whether the edge will become a crease or not = 1 MESH = 000000010 #The edge is an original poly edge, before triangulation = 2 HIDDEN = 000000100 #The edge has been occluded = 4 SILHOUETTE = 000001000 #Silhouette edge, between front-facing and back-facing polygons = 8 DRAWN = 000010000 #Flag that indicates that the edge has been drawn = 16 CLIPPED = 000100000 #Indicates the edge has been visited by the 2d clipping algorithm = 32 CUT = 001000000 #An edge that has been clipped by a Cutaway or Vignette = 64 CUTISVISIBLE = 010000000 #determines whether a cut edge should be shown as a Cut or Silhouette = 128 NONMANIFOLD = 100000000 #flag telling us that the edge is spawned because a non-manifold edge has been encountered #Installation settings############### LIB_VERSION = "0.6" #---GEOMETRY CLASSES---#000000#FFFFFF----------------------------- class Scene: def __init__(self): self.meshes = 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 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]) 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) 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]) class Mesh: """ a 2d or 3d collection of faces and curves """ def __init__(self,scene,name): self.scene = scene self.name = name self.creaseAngle = 30 #this value gets set according to the Blender's "Auto smooth" value self.layers = None #the Blender layers which this mesh is on self.attributes = 00000000 self.faces = set() self.polylines = set() self.scene.meshes.add(self) class Face: """ a 2d or 3d region bounded by clockwise edges contains: material information, a link to the first half-edge BITCODES: FRONT_FACING = 00000001 #Face normal faces towards the camera = 1 BACK_FACING = 00000010 #Face normal faces away from camera = 2 GEOMETRY_TYPE = 00000100 CUTTING_TYPE = 00001000 PARTITION_TYPE =00010000 FRUSTUM_TYPE = 00100000 VIGNETTE_TYPE = 01000000 HIDDEN = 10000000 """ def __init__(self, mesh, materialName = None, normal = False, facing = FRONT_FACING, type = GEOMETRY_TYPE): self.attributes = type & facing self.mesh = mesh self.scene = mesh.scene self.materialName = materialName self.normal = normal #these all need to be set eventually self.bbox = None self.first = None #the first half-edge of the chain self.mesh.faces.add(self) def getVerts(self): """ returns a list of verts making up the face """ verts = list() halfEdge = self.first while vert is not self.vert[0]: vert = halfEdge.root verts.append(vert) halfEdge = halfEdge.next return verts class Polyline: """ a non-closed set of half-edges: while these get clipped, they do not occlude other objects. """ def __init__(self, mesh, materialName = None): self.mesh = mesh self.scene = mesh.scene self.materialName = materialName #this is the first half-edge in the curve sequence self.first = None self.mesh.polylines.add(self) class Region: """ A 2d 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 = 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 HalfEdge: """ contains connectivity data for the edge """ def __init__(self, face, edge, vert): self.scene = edge.scene self.root = vert self.edge = edge self.face = face self.next = None #the next half-edge in the chain self.twin = self.edge.halfEdges.difference([self]) #the half-edge pointing in the opposite direction class Edge: """ contains attribute data for the edge BITCODES: CREASE = 00000001 #Determines whether the edge will become a crease or not = 1 MESH = 00000010 #The edge is an original poly edge, before trinagulation = 2 HIDDEN = 00000100 #The edge has been occluded = 4 SILHOUETTE = 00001000 #Silhouette edge, between front-facing and back-facing polygons = 8 DRAWN = 00010000 #Flag that indicates that the edge has been drawn = 16 CLIPPED = 00100000 #Indicates the edge has been visited by the 2d clipping algorithm = 32 CUT = 01000000 #An edge that has been clipped by a Cutaway or Vignette = 64 CUTISVISIBLE = 10000000 #determines whether a cut edge should be shown as a Cut or Silhouette = 128 """ def __init__(self, vertList, mesh): self.scene = mesh.scene self.halfEdges = set() self.bbox = None self.attribute = 00000000 class Vert: def __init__(self, mesh, loc = None, loc2d = None): self.scene = mesh.scene self.loc = loc self.loc2d = loc2d self.halfEdges = set() #a set of outgoing halfEdges self.scene.faces.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 #---GEOMETRY FUNCTIONS---#000000#FFFFFF----------------------------- def classifyFaceFace(face, otherFace, cMap=False): """ classifies the face as IN_FRONT, BEHIND or ON. sets classifyDict with classifications for all the verts. """ #Make a dictionary with the classification of each vert in face if not cMap: cMap = [classifyPointPlane(vert.loc, otherFace.plane) for vert in face.getVerts()] rev = not otherFace.facing is FRONT_FACING if IN_FRONT in cMap: if BEHIND in cMap: return INTERSECTING elif rev: return BEHIND else: return IN_FRONT elif BEHIND in cMap: if rev: return IN_FRONT else: return BEHIND else: return ON def classifyPointFace(vec, face): """ 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 = face.first.root - vec D = DotVecs(face.normal,w) if abs(D) < EPSILON3D: return ON elif D > EPSILON3D: return IN_FRONT else: return BEHIND def transform2Window(vert, matrix, WIDTH, HEIGHT): """ Returns window coordinates for a vertex, if that location has not yet been set """ if not vert.loc2d: (initial_x, initial_y, initial_z, initial_w) = VecMultMat(vert.loc.resize4D(), matrix) #convert to homogenous (-1,1) components result_x = initial_x / initial_w result_y = initial_y / initial_w #convert to window coordinates result_x = ((WIDTH / 2) * result_x) + (WIDTH / 2) result_y = ((HEIGHT / 2) * -result_y) + (HEIGHT / 2) vert.loc2d = Vector(result_x,result_y) def segmentIntersection(edge, face): """ 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 = halfEdge.next.root.loc - halfEdge.root.loc w = halfEdge.root.loc - face.start.root D = DotVecs(face.normal,u) N = DotVecs(-face.normal,u) if abs(D) < EPSILON3D: return False #edge is parallel to the plane sI=N / D if sI < -EPSILON3D or sI > 1 + EPSILON3D: return False #intersection point not on edge elif sI < EPSILON3D: #intersection point on vert0 intP = halfEdge.root elif sI > 1 - EPSILON3D: intP = halfEdge.next.root else: I = halfEdge.root.loc + (sI * u) intP = Vert(loc=I ) return intP def splitEdgeFace(halfEdge, 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. Also splits the adjoining halfEdge at the same point, and updates the edge topology. Returns two halfEdges, order in the direction of the original halfEdge """ intP = segmentIntersection(edge, face) if not intP: #no intersection or the edge is parallel to the face return (False, False) r2 = classifyDict[halfEdge.root] r1 = classifyDict[halfEdge.next.root] if intP is halfEdge.root: #the intersectionPt is one of our endpoints return (None, halfEdge) elif intP is halfEdge.next.root: return (halfEdge, None) #We must have a proper intersection, with two resulting segments: edge1 = copy(halfEdge.edge) edge2 = copy(halfEdge.edge) halfEdge1Forward = halfEdge halfEdge1Forward.edge = edge1 halfEdge2Backward = halfEdge.twin halfEdge2Backward.edge = edge2 halfEdge1Backward = HalfEdge(face, edge1, intP) halfEdge1Forward.twin = halfEdge1Backward halfEdge1Backward.twin = halfEdge1Forward halfEdge1Backward.next = halfEdge.twin.next halfEdge2Forward = HalfEdge(face, edge2, intP) halfEdge2Backward.twin = halfEdge2Forward halfEdge2Forward.twin = halfEdge2Backward halfEdge2Forward.next = halfEdge.next halfEdge1Forward.next = halfEdge2Forward halfEdge2Backward.next = halfEdge2Backward edge1.halfEdges = set([halfEdge1Forward, halfEdge1Backward]) edge2.halfEdges = set([halfEdge2Forward, halfEdge2Backward]) return (halfEdge1Forward, halfEdge2Forward) def splitFaceFace(face, otherFace, cMap=False): """ Splits a face with another face, resulting in a tuple of: (front face, behind faces) In cases where there is no split, returns (None, behind face) or (front face, None) In cases where the face is parallel to and on the plane, returns (False, False) Attributes of split faces will match the original face """ if not cMap: cMap = [classifyPointPlane(vert.loc, otherFace.plane) for vert in face.verts] r = classifyFaceFace(face,otherFace,cMap) #first check to see if we can classify the face as all on one side or the other if r is IN_FRONT: return (face, None) elif r is BEHIND: return (None, face) #we have a split resulting in two new faces, so proceed with splitting global SPLIT_CALLS SPLIT_CALLS += 1 classifyDict = dict(zip(face.getVerts(),cMap)) #make copies of the current face and detach the edges frontFace = face.copy() frontFace.root = None backFace = face.copy() backFace.root = None intersectionEdge = Edge(face.mesh) currentFace = classifyDict[face.first.root] #the current face we're adding to currentHalfEdge = face.first startingHalfEdge = face.first frontCurrentHalfEdge = None #the "tail" of the front face backCurrentHalfEdge = None #the "tail of the back face while currentHalfEdge is not startingHalfEdge.next: #check the status of the current halfedge myStatus = classifyDict[currentHalfEdge.root] | classifyDict[currentHalfEdge.next.root]#bitwise if myStatus is IN_FRONT: currentFace = IN_FRONT if not frontCurrentHalfEdge: frontCurrentHalfEdge = currentHalfEdge else: frontCurrentHalfEdge.next = currentHalfEdge frontCurrentHalfEdge = frontCurrentHalfEdge.next elif myStatus is BEHIND: currentFace = BEHIND if not backCurrentHalfEdge: backCurrentHalfEdge = currentHalfEdge else: backCurrentHalfEdge.next = currentHalfEdge backCurrentHalfEdge = backCurrentHalfEdge.next elif myStatus is INTERSECTING: #split the edge by our face and return two halfEdges - the order is determined by the direction of the halfEdge going in #crossing the intersection, we add both halves to the face we're leaving, but only the second to the one we're entering #(the gap will get closed at the end) mySplitHalfEdges = splitHalfEdgeFace(currentHalfEdge, face, classifyDict) if currentFace is IN_FRONT: if not frontCurrentHalfEdge: frontCurrentHalfEdge = mySplitHalfEdges[0] else: frontCurrentHalfEdge.next = mySplitHalfEdges[0] frontCurrentHalfEdge = frontCurrentHalfEdge.next frontCurrentHalfEdge.next = mySplitHalfEdges[1] frontCurrentHalfEdge = frontCurrentHalfEdge.next if not backCurrentHalfEdge: backCurrentHalfEdge = mySplitHalfEdges[1] else: backCurrentHalfEdge.next = mySplitHalfEdges[1] backCurrentHalfEdge = backCurrentHalfEdge.next currentFace = BEHIND elif currentFace is BEHIND: if not backCurrentHalfEdge: backCurrentHalfEdge = mySplitHalfEdges[0] else: backCurrentHalfEdge.next = mySplitHalfEdges[0] backCurrentHalfEdge = frontCurrentHalfEdge.next backCurrentHalfEdge.next = mySplitHalfEdges[1] backCurrentHalfEdge = frontCurrentHalfEdge.next if not frontCurrentHalfEdge: frontCurrentHalfEdge = mySplitHalfEdges[1] else: frontCurrentHalfEdge.next = mySplitHalfEdges[1] frontCurrentHalfEdge = backCurrentHalfEdge.next currentFace = IN_FRONT else: #we must have one vert that is ON if myStatus & IN_FRONT:#bitwise if not frontCurrentHalfEdge: frontCurrentHalfEdge = currentHalfEdge else: frontCurrentHalfEdge.next = currentHalfEdge frontCurrentHalfEdge = frontCurrentHalfEdge.next elif myStatus & BEHIND: #bitwise if not backCurrentHalfEdge: backCurrentHalfEdge = currentHalfEdge else: backCurrentHalfEdge.next = currentHalfEdge backCurrentHalfEdge = backCurrentHalfEdge.next #increment current to the next halfEdge currentHalfEdge = currentHalfEdge.next #now we connect the edges to make a complete face if otherFace.facing is BACK_FACING: frontFace, backFace = backFace, frontFace #---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.fovx = blenderCamera.getData().angle scene.near = blenderCamera.getData().clipStart scene.far = blenderCamera.getData().clipEnd scene.scale = blenderCamera.getData().scale #generate the necessary frustum points: if blenderCamera.getData().type == 'ortho': scene.cam2ClipTransform = scene.getOrthoMatrix(scene.aspect,scene.near,scene.far,scene.scale) scene.projection = 'ortho' scene.cameraLoc = Vector(blenderCamera.loc) * scene.cameraMatrix scene.cameraViewVector = -Vector(0,0,1) scene.cameraUpVector = Vector(0,1,0) scene.cameraRightVector = Vector(1,0,0) scene.nearCenter = cameraLoc + cameraViewVector * scene.near scene.nearWidth = float(scene.scale) scene.nearHeight = float(scene.scale/scene.aspect) scene.farCenter = (cameraLoc + cameraViewVector) * scene.far scene.farWidth = nearWidth scene.farHeight = nearHeight elif blenderCamera.getData().type == 'persp': scene.cam2ClipTransform = scene.getPerspMatrix(scene.fovx,scene.aspect,scene.near,scene.far) scene.projection = 'persp' scene.cameraLoc = Vector(blenderCamera.loc) * scene.cameraMatrix scene.cameraViewVector = -Vector(0,0,1) scene.cameraUpVector = -Vector(0,1,0) scene.cameraRightVector = -Vector(1,0,0) scene.nearCenter = cameraLoc + cameraViewVector * scene.near scene.nearWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.near scene.nearHeight = nearWidth / scene.aspect scene.farCenter = (cameraLoc + cameraViewVector) * scene.far scene.farWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.far scene.farHeight = farWidth / scene.aspect #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 = set([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() #Get the Matrix of the Object objMatrix = blenderObj.getMatrix().copy() identityMatrix = Blender.Mathutils.Matrix().identity() #set the object's matrix to the identity proxyObj.setMatrix(identityMatrix) #transform the mesh by the object's matrix to get World Space, and by the inverse camera matrix to get camera space myTransform = objMatrix * scene.cameraMatrix blenderMesh.transform(myTransform) #VERTS#################### #vertDict is a dict of { progress("Processing %i vertices..." % len(blenderMesh.verts)) vertList = [Vert(scene, loc = Vector(vert.co)) for vert in blenderMesh.verts] setItem = vertDict.__setitem__ [setItem(blenderVert, vert) for (blenderVert, vert) in zip(blenderMesh.verts, vertList)] #EDGES#################### #edgeDict is a dict of {frozenset of edgeVerts:edge} progress("Processing %i edges..." % len(blenderMesh.edges)) edgeList = [Edge(mesh) for vertList in [(vertDict[edge.v1], vertDict[edge.v2]) for edge in blenderMesh.edges]] setItem = edgeDict.__setitem__ [setItem(frozenset((vertDict[edge.v1], vertDict[edge.v2])),edge) for edge in edgeList] for (blenderEdge, edge) in zip(blenderMesh.edges, edgeList): if blenderEdge.flag & Blender.Mesh.EdgeFlags.SHARP: edge.attributes = edge.attributes | CREASE elif blenderEdge in blenderMeshEdges: edge.attributes = edge.attributes | MESH #FACES/CURVES#################### progress("Processing %i faces..." % len(blenderMesh.faces)) faceList = [Face(mesh, normal = blenderFace.normal) for blenderFace in blenderMesh.faces] setItem = faceDict.__setitem__ [setItem(blenderFace,face) for (blenderFace, face) in zip(blenderMesh.faces, faceList)] for blenderFace in blenderMesh.faces: blenderVerts = list(blenderFace.verts) #make the first halfEdge in the face myEdge = edgeDict[frozenset((vertDict[blenderVerts[0]],vertDict[blenderVerts[1]]))] ###experimental - this is to deal with non-manifold meshes! if len(myEdge.halfEdges) == 2: myEdge = copy(myEdge) myEdge.attributes = myEdge.attributes | NONMANIFOLD myFace = faceDict[blenderFace] myHalfEdge = HalfEdge(myFace, myEdge, vertDict[blenderVerts[0]]) myFace.first = myHalfEdge #iterate through the verts, making new spans and attaching them to the chain for (blenderVert, nextBlenderVert) in zip(blenderVerts[1:], (blenderVerts[2:] + blenderVerts[0])): myEdge = edgeDict[frozenset((vertDict[blenderVert],vertDict[nextBlenderVert]))] ###experimental - this is to deal with non-manifold meshes! if len(myEdge.halfEdges) == 2: myEdge = copy(myEdge) myEdge.attributes = myEdge.attributes | NONMANIFOLD myHalfEdge.next = HalfEdge(myFace, myEdge, vertDict[blenderVert]) myHalfEdge = myHalfEdge.next #connect the last halfEdge to the beginning myHalfEdge.next = myFace.first #determine if the face is FRONT- or BACK-FACING if scene.projection == 'persp': #we can determine the facing by checking the classification of the camera loc classify = classifyPointFace(scene.camera, myFace) if classify == IN_FRONT: face.attributes = face.attributes | FRONT_FACING else: face.attributes = face.attributes | BACK_FACING elif scene.projection == 'ortho': #we can determine the facing by comparing the face normal and the camera view vector facingAngle = AngleBetweenVecs(myFace.normal, scene.cameraViewVector) if facingAngle > 90: face.attributes = face.attributes | FRONT_FACING else: face.attributes = face.attributes | BACK_FACING #set bounding box for each face myFace.bbox = BoundingBox(face.getVerts()) #Go through edgeList: edges with only one halfEdge are silhouette edges, edges with no halfEdges are polyline edges polylineSet = set() for edge in edgeList: if edge.halfEdges: if len(edge.halfEdges) == 1 and not (edge.attributes & NONMANIFOLD): #NONMANIFOLD edges are not marked as SILHOUETTE edges here edge.attributes = edge.attributes | SILHOUETTE elif len(edge.halfEdges) == 2: #edges with adjoining faces of different facings are silhouettes facingList = [halfEdge.face.attributes for halfEdge in edge.halfEdges] if (facingList[0] | facingList[1]) & (FRONT_FACING & BACK_FACING): edge.attributes = edge.attributes | SILHOUETTE else: polylineSet.add(edge) #Figure out how to string edges together into longer polylines... mesh.polylines = polylineSet #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.attributes = face.attributes | CUTTING_TYPE mesh.attributes = mesh.attributes | CUTTING_TYPE face.materialName = 'DEFAULT' elif myMaterial.name == "VIGNETTE" or myMaterial.name == "Vignette" or myMaterial.name == "vignette":#for vignette faces face.attributes = face.attributes | VIGNETTE_TYPE mesh.attributes = mesh.attributes | VIGNETTE_TYPE face.materialName = 'DEFAULT' elif myMaterial: face.materialName = myMaterial.name else: #this covers objects with no material face.materialName = 'DEFAULT' elif mesh.polylines: blenderCurve = blenderObj.data for polyline in mesh.polyline: 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 polyline.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 polyline.materialName = 'DEFAULT' elif myMaterial: polyline.materialName = myMaterial.name else: #this covers objects with no material polyline.materialName = 'DEFAULT' #BACKFACE CULLING#################### if (settings.CULL_BACKFACES) and mesh.faces: progress("Culling back-facing triangles...") ##MAKE THIS WORK!!! ## 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) #AUTO-CREASING#################### if (settings.AUTO_CREASE) and mesh.faces: progress("Culling back-facing triangles...") ## 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] 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 makeFrustum(scene): """ Creates a frustum cube for a frustum cull """ progress("Creating the camera frustum...") #generate the necessary frustum points: 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 polylineList = [] ## for mesh in scene.meshes: ## if mesh.curves: ## polylineList.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 [transform2Window(vert, scene.cam2ClipTransform, drawing.WIDTH, drawing.HEIGHT) for vert in 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 frame to an swf movie """ #initialize the shape queue, if necessary ## 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(): for mesh in self.fillDict[material].keys(): #myShape = shape.next() myShape = ming.SWFShape() 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"]] ## 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() myShape = ming.SWFShape() #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.attributeDict["MATERIAL SILHOUETTE"]["Visible"]: #myShape = shape.next() myShape = ming.SWFShape() #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.attributeDict["MATERIAL SILHOUETTE"]["Visible"]: #myShape = shape.next() myShape = ming.SWFShape() #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.attributeDict["MESH"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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.attributeDict["CREASE"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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.attributeDict["SILHOUETTE"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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.attributeDict["CUT"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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.attributeDict["HIDDEN"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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.attributeDict["CURVE"]["Visible"]: #myShape = shape.next() #movie.remove(myShape) myShape = ming.SWFShape() 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) myShape = ming.SWFShape() 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()