# ----------------------------------------------------------------------------- # 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, DotVecs 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 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 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." 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------------------------------------------- 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############### 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 D = DotVecs(plane.normal,w) if abs(D) < EPSILON3D: return ON elif D > EPSILON3D: return IN_FRONT else: return BEHIND 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.verts] 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 splitFaceFace(face, otherFace, cMap=False): """ 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! """ if not cMap: cMap = [classifyPointPlane(vert.loc, otherFace.plane) for vert in face.verts] r = classifyFaceFace(face,otherFace,cMap) if not r is INTERSECTING: return r ###We know that we have a split resulting in two new faces classifyDict = dict(zip(face.verts,cMap)) #rather than making TWO copies, frontFace points to the original face frontFace = face backFace = copy(face) intersectionVerts = list() edgeDestroySet = set() frontFaceVertsRemoveSet = set() backFaceVertsRemoveSet = set() frontFaceEdgeRemoveSet = set() backFaceEdgeRemoveSet = set() for myEdge in copy(face.edges): #why copy() here? vert = myEdge.verts[0] nextVert = myEdge.verts[1] if classifyDict[vert] is ON: #this is an intersection pt if not (vert in intersectionVerts): intersectionVerts.append(vert) elif classifyDict[nextVert] is ON: #this is an intersection pt if not (nextVert in intersectionVerts): intersectionVerts.append(nextVert) elif abs(classifyDict[vert] - classifyDict[nextVert]) == 2: #nice trick! or: one is IN_FRONT, the other BEHIND visEdge, hidEdge, intP = splitEdgeFace(myEdge, otherFace, classifyDict) if visEdge is False: raise "No split where there should have been one!" if visEdge and hidEdge: edgeDestroySet.add(myEdge) classifyDict[intP] = ON elif visEdge is None: backFaceEdgeRemoveList.append(visEdge) else: frontFaceEdgeRemoveList.append(hidEdge) intersectionVerts.append(intP) [myEdge.destroy() for myEdge in edgeDestroySet] #Check the classification of each vert, and remove it from the face on the opposite side backFaceVertsRemoveList = [vert for vert in face.verts if classifyDict[vert] is IN_FRONT ] frontFaceVertsRemoveList = [vert for vert in face.verts if classifyDict[vert] is BEHIND ] #Check the classification of each edge, and remove it from the face on the opposite side for edge in face.edges: v0 = edge.verts[0] v1 = edge.verts[1] if v0 not in intersectionVerts and classifyDict[v0] is IN_FRONT: if v1 in intersectionVerts or not classifyDict[v1] is BEHIND: if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge) elif v0 not in intersectionVerts and classifyDict[v0] is BEHIND: if v1 in intersectionVerts or not classifyDict[v1] is IN_FRONT: if not edge in frontFaceEdgeRemoveList: frontFaceEdgeRemoveList.append(edge) elif v0 in intersectionVerts or classifyDict[v0] is ON: if v1 in intersectionVerts or classifyDict[v1] is IN_FRONT: if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge) elif v1 in intersectionVerts or classifyDict[v1] is 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 is BACK_FACING: #If the cut edge is visible to the camera, mark it so a cutaway would be visible intersectionEdge.CUTISVISIBLE = True #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 is BACK_FACING: frontFace, backFace = backFace, frontFace return (frontFace, backFace) def splitCurveFace(curve, otherFace): """ If intersecting, returns a tuple of (back curveList, on curveList, front curveList). """ cMap = [classifyPointPlane(vert.loc, otherFace.plane) for vert in curve.verts] r = classifyFaceFace(curve,otherFace,cMap) if not r is INTERSECTING: return r #we have a split, so we need to discover where the actual intersection(s) are/is classifyDict = dict(zip(curve.verts,cMap)) 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 is IN_FRONT: if classifyDict[vert] is BEHIND: if classifyDict[lastVert] is IN_FRONT: visEdge, hidEdge, intP = splitEdgeFace(edge, otherFace, classifyDict) if visEdge is False: mySegment.append(verts=[vert], edges=[edge]) elif hidEdge is None: #our split results in only a front edge mySegment.append(verts=intP, edges=visEdge) frontCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP], curve.materialName) elif visEdge is None: mySegment.append(verts=intP, edges=hidEdge) backCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP], curve.materialName) else: #we have a normal split, resulting in two distinct edges mySegment.append(verts=intP, edges=visEdge) frontCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP, vert], curve.materialName) mySegment.append(edges = [hidEdge]) elif classifyDict[lastVert] is 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] < BEHIND: mySegment.append(verts = [vert], edges = [edge]) elif flag is BEHIND: if classifyDict[vert] is IN_FRONT: if classifyDict[lastVert] is BEHIND: visEdge, hidEdge, intP = splitEdgeFace(edge, otherFace, classifyDict) if visEdge is False: mySegment.append(verts = [vert], edges = [edge]) elif visEdge is None: #our split results in only a back edge mySegment.append(verts=intP, edges=hidEdge) backCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP], curve.materialName) elif hidEdge is None: #our split results in only a front edge mySegment.append(verts = intP, edges=visEdge) frontCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP], curve.materialName) else: #we have a normal split, resulting in two distinct edges mySegment.append(verts=intP, edges=[hidEdge]) frontCurveList.append(copy(mySegment)) mySegment = Curve(curve.mesh, [intP,vert], curve.materialName) mySegment.append(edges=[visEdge]) elif classifyDict[lastVert] is 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 not classifyDict[vert] is IN_FRONT: mySegment.append(verts=[vert], edges=[edge]) elif flag is ON: if classifyDict[vert] is IN_FRONT: mySegment.append(verts=[vert], edges=[edge]) flag = IN_FRONT elif classifyDict[vert] is BEHIND: mySegment.append(verts=[vert], edges=[edge]) flag = BEHIND else: mySegment.append(verts=[vert], edges=[edge]) if flag is IN_FRONT: frontCurveList.append(copy(mySegment)) elif flag is BEHIND: backCurveList.append(copy(mySegment)) elif flag is ON: onCurveList.append(copy(mySegment)) if otherFace.facing is 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 < -EPSILON3D or sI > 1 + EPSILON3D: return False #intersection point not on edge elif sI < EPSILON3D: #intersection point on vert0 intP = edge.verts[0] elif sI > 1 - EPSILON3D: intP = edge.verts[1] else: I = edge.verts[0].loc + (sI * u) intP = Vert(edge.scene, loc=I ) return intP 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) """ intP = segmentIntersection(edge, face.plane) if not intP: #no intersection or the edge is parallel to the face return False, False, False r2 = classifyDict[edge.verts[1]] r1 = classifyDict[edge.verts[0]] if intP is edge.verts[0]: #the intersectionPt is one of our endpoints if r2 is IN_FRONT: return edge, None, intP else: return None, edge, intP elif intP is edge.verts[1]: if r1 is IN_FRONT: return edge, None, intP else: return None, edge, intP ###The key here is to insert the edges in the proper order into the adjoining faces: ###but - that order is going to be different for each face! #We must have a proper intersection, with two resulting segments: if r1 == 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, intP] backEdge = copy(edge) backEdge.verts = [intP,backVert] intP.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=[intP]) else: myFace.insert(max(firstVertIndex,secondVertIndex),verts=[intP]) return frontEdge,backEdge,intP #---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.zMaxvert.loc.x or self.xMin is NOT_SET:self.xMin=vert.loc.x if self.xMaxvert.loc.y or self.yMin is NOT_SET:self.yMin=vert.loc.y if self.yMaxvert.loc2d.x or self.xMin is NOT_SET:self.xMin=vert.loc2d.x if self.xMaxvert.loc2d.y or self.yMin is NOT_SET:self.yMin=vert.loc2d.y if self.yMax 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.insert(face.edges.index(self),newEdge) for face in self.faces] #inserts the new edge before the original 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 from isLeft from geometry algorithms ''' lp0 = segment[0].loc2d lp1 = segment[1].loc2d p = vert.loc2d t = (lp1.x - lp0.x) * (p.y - lp0.y) - \ (p.x - lp0.x) * (lp1.y - lp0.y) return abs(t) < E 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 #we are using Decimal to quantize mu to EPSILON2D intersectionPoint_x = Decimal(str(edge.verts[0].loc2d.x + ua*(edge.verts[1].loc2d.x - edge.verts[0].loc2d.x))).quantize(Decimal(str(EPSILON2D))) intersectionPoint_y = Decimal(str(edge.verts[0].loc2d.y + ua*(edge.verts[1].loc2d.y - edge.verts[0].loc2d.y))).quantize(Decimal(str(EPSILON2D))) #ua should be the mu value for the intersection point return (ua, (float(intersectionPoint_x), float(intersectionPoint_y))) #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 = set([intersectEdgeSegment(edge, segment) for segment in segmentList]) intersectPoints = filter(None, intersectPoints) #discard non-intersections by removing False results intersectPoints = [tuple for tuple in intersectPoints if (tuple[0] > EPSILON2D and tuple[0] < (1 - EPSILON2D))] 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 = Vector(loc[0], loc[1])) 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(vertList[0].loc2d, vertList[1].loc2d) if region.isInside(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: debug(key, len(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 for direction in range(2): if edgeSet: #if we have more segments, try to add them currentVert = nextVert = myPolyline.verts[-1] while nextVert: #if we found a next segment, keep going debug('nextvert:',nextVert) nextVert = None for edge in currentVert.edges: debug('checking edge:', edge) if edge in edgeSet: debug('found one:', edge) edgeSet.remove(edge) nextVert = [vert for vert in edge.verts if vert is not 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 = 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.loc2d.x, vert.loc2d.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 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()