# -----------------------------------------------------------------------------
# Copyright (c) 2007, 2008 by Severn Clay
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# -----------------------------------------------------------------------------
import Blender
from Blender import Mesh
from Blender.Mathutils import CrossVecs, Vector, Matrix, AngleBetweenVecs, MidpointVecs
try:
from scipy import weave
WEAVE = True
except:
print('Weave library not found - you will experience somewhat slower rendering times, sorry.')
WEAVE = False
try:
import ming
except:
print('Ming library not found - you will not be able to render animations')
from copy import deepcopy, copy
from bisect import insort
from math import tan,atan,pi,degrees,sqrt,radians
from time import clock
from itertools import repeat
import cPickle
import sys, os, gc, types
try:
import cairo
except:
print "You need to install Cairo",
print "or set your PYTHONPATH correctly."
print "try: export PYTHONPATH=/usr/lib/python2.4/site-packages",
sys.exit(1)
try:
from Polygon import *
except:
print "Make sure Polygon is installed and properly compiled"
print "and linked for your installation of Python"
print "http://download.dezentral.de/soft/Python/Polygon/"
#---GLOBAL VARIABLES---#000000#FFFFFF-------------------------------------------
#Run-time settings###################
DEBUG = False
PROGRESS = True
PROFILE = True
#Global variables####################
CLASSIFY_CALLS = 0
SPLIT_CALLS = 0
MAX_DEPTH = 0
PROCESSED_FACES = 0
TOTAL_FACES = 0
CLIP_COUNTER = 0
EPSILON3D = .0001
EPSILON2D = .0001
setTolerance(EPSILON2D) #this controls the tolerance of the Polygon package
RECURSION_LIMIT = 100 #this is set to prevent the BSP algorithm from over-recursing
#Spacial Classification##############
NOT_SET = None
IN_FRONT = 1000
ON = 1001
BEHIND = 1002
INTERSECTING = 1003
#Face Types##########################
GEOMETRY_TYPE = 1004
CUTTING_TYPE = 1005
PARTITION_TYPE = 1006
FRUSTUM_TYPE = 1007
VIGNETTE_TYPE = 1008
FRONT_FACING = 1009
BACK_FACING = 1010
#User Variables######################
#These will be user-settable#########
CULL_BACKFACES = False
USE_ALPHA = True
RENDER_ANIMATION = False
AUTO_CREASE = False
LOAD_PREVIOUS_SETTINGS = True
#Installation settings###############
##!!! Homedir should eventually be set by the installation program !!!
HOMEDIR = "/home/agiofws/zips/panto/"
LIB_VERSION = "0.5"
#---UTILITY CLASSES AND FUNCTIONS---#000000#FFFFFF--------------------------------
def debug(*args):
if DEBUG: print args
def progress(*args):
if PROGRESS: print args
def distance(start,end):
"""
returns the signed distance between two vectors(x,y,z)
by taking the sqrt of the dot product of the difference of the two vectors with itself
"""
difference = (start - end)
result = sqrt(difference * difference)
return result
def intersect(firstList,secondList):
"""
returns the intersection between two lists or tuples
"""
return [item for item in firstList if item in secondList]
class Profiler:
"""
A utility class intended for timing code blocks
"""
def __init__(self,name):
self.total = 0
self.begin = 0
self.name = name
def start(self):
if not self.begin and PROFILE:
self.begin = clock()
def end(self):
if self.begin and PROFILE:
self.total = self.total + (clock() - self.begin)
self.begin = 0
return (self.name + " took %.3f minutes" % (self.total/60.00))
#---3D GEOMETRY FUNCTIONS---#000000#FFFFFF-----------------------------
def doCrease(edge, angle):
"""
Marks the edge as creased if the incident faces are less than the given angle
"""
try:
if (AngleBetweenVecs(edge.faces[0].normal, edge.faces[1].normal) > angle) and (not edge.SILHOUETTE):
edge.SHARP = True
else:
edge.SHARP = False
except:
edge.SHARP = False
def doCornerDetection(edge, angle):
"""
Find edge neighbours for the edge, and classify them as hard or soft
If SHARP and not SHARP: hard connection
If SHARP and SHARP: soft if no other SHARP, else hard
IF not SHARP and not SHARP: "
All connections with edges of a different type are hard.
"angle" is the angle at which an edge is automatically creased
"""
if edge.SILHOUETTE:
for vert in edge.verts:
if otherEdge in vert.edges:
if otherEdge != edge:
print("This isn't finished yet")
def computeNormal(face):
"""
Returns a normalized vector of a face's normal
"""
vec1 = Vector(face.verts[1].loc.x,face.verts[1].loc.y,face.verts[1].loc.z)-Vector(face.verts[0].loc.x,face.verts[0].loc.y,face.verts[0].loc.z)
vec2 = Vector(face.verts[2].loc.x,face.verts[2].loc.y,face.verts[2].loc.z)-Vector(face.verts[0].loc.x,face.verts[0].loc.y,face.verts[0].loc.z)
normal = CrossVecs(vec1, vec2)
#normal = normal.normalize()
normal = Vector(normal.x, normal.y, normal.z, 0)
face.normal = normal
face.plane = Plane(face.normal,face.verts[0].loc)
def clip2Window(vert, drawing):
"""
Returns window coordinates for a vertex
"""
WIDTH = drawing.WIDTH
HEIGHT = drawing.HEIGHT
if not vert.loc2d:
#convert to homogenous (-1,1) components
vert.loc2d = Vector(0,0)
vert.loc2d.x = vert.loc.x / vert.loc.w
vert.loc2d.y = vert.loc.y / vert.loc.w
#convert to window coordinates
vert.loc2d.x = ((WIDTH/2) * vert.loc2d.x) + (WIDTH / 2)
vert.loc2d.y = ((HEIGHT/2) * -vert.loc2d.y) + (HEIGHT / 2)
def classifyPointPlane(vec, plane):
"""
Classify the given point with respect to a plane:
Returns
IN_FRONT if in front of plane,
ON if on the plane ,
BEHIND if behind the plane
modeled after
http://mathworld.wolfram.com/Point-PlaneDistance.html
"""
global CLASSIFY_CALLS
CLASSIFY_CALLS += 1
w = plane.point - vec
dist = (plane.normal * w) / plane.normal.length
if abs(dist) < EPSILON3D:
return ON
elif dist > EPSILON3D:
return IN_FRONT
else:
return BEHIND
def classifyFaceFace(face, otherFace, classifyDict):
"""
classifies the face as IN_FRONT, BEHIND or ON.
sets classifyDict with classifications for all the verts.
"""
intersectionVerts = []
#Make a dictionary with the classification of each vert in face
[classifyDict.__setitem__(vert, classifyPointPlane(vert.loc, otherFace.plane)) for vert in face.verts]
front = False
back = False
for vert in classifyDict.keys():
if front and back: break #we're already straddling the plane
if classifyDict[vert]==IN_FRONT:
front=True
elif classifyDict[vert]==BEHIND:
back=True
elif classifyDict[vert]==ON:
pass
if not front and not back:
return ON
elif front and back:
return INTERSECTING
elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
return IN_FRONT
elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
return BEHIND
def splitFaceFace(face, otherFace, classifyDict):
"""
First attempts to classify the first face in front or behind the other.
NOTE: This takes into account whether the splitting face is front-facing or back-facing!
Returns IN_FRONT or BEHIND if not intersecting.
If intersecting, returns a tuple of(back polygon, front polygon).
Assumes that both polys are convex. Maintains proper vertex order!
"""
###Attempt to classify the face according to the splitting face
front=False
back=False
for vert in face.verts:
if front and back:
break #we're already straddling the plane
elif classifyDict[vert]==IN_FRONT:
front=True
elif classifyDict[vert]==BEHIND:
back=True
elif classifyDict[vert]==ON:
pass
if not front and not back:
return ON #this would occur only if the faces are parallel and coincident
elif front and back:
pass
elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
return IN_FRONT
elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
return BEHIND
###We know that we have a split resulting in two new faces
frontFace = face #rather than making TWO copies, frontFace points to the original face
backFace = copy(face)
intersectionVerts = list()
edgeDestroySet = set()
frontFaceVertsRemoveList = list()
backFaceVertsRemoveList = list()
frontFaceEdgeRemoveList = list()
backFaceEdgeRemoveList = list()
for myEdge in copy(face.edges):
vert = myEdge.verts[0]
nextVert = myEdge.verts[1]
if classifyDict[vert] == ON: #this is an intersection pt
if (not vert in intersectionVerts):
intersectionVerts.append(vert)
elif classifyDict[nextVert] == ON: #this is an intersection pt
if (not nextVert in intersectionVerts):
intersectionVerts.append(nextVert)
elif (classifyDict[vert] == IN_FRONT and classifyDict[nextVert] == BEHIND) or \
(classifyDict[vert] == BEHIND and classifyDict[nextVert] == IN_FRONT):
splitEdges = splitEdgeFace(myEdge, otherFace, classifyDict)
if splitEdges:
try: #our edge is split into two edges
intersectionPt = intersect(splitEdges[0].verts, splitEdges[1].verts)[0]
edgeDestroySet.add(myEdge)
except: #this edge has already been split, or is being split on one of its endpoints
intersectionPt = splitEdges[2]
if splitEdges[0]:
backFaceEdgeRemoveList.append(splitEdges[0])
elif splitEdges[1]:
frontFaceEdgeRemoveList.append(splitEdges[1])
else:
raise "No split where there should have been one!"
classifyDict[intersectionPt] = ON
intersectionVerts.append(intersectionPt)
[myEdge.destroy() for myEdge in edgeDestroySet]
#Check the classification of each vert, and remove it from the face on the opposite side
for vert in face.verts:
if not vert in intersectionVerts: #the intersection verts haven't been classified, and are in both faces anyway
if classifyDict[vert] == IN_FRONT:
backFaceVertsRemoveList.append(vert)
elif classifyDict[vert] == BEHIND:
frontFaceVertsRemoveList.append(vert)
#Check the classification of each edge, and remove it from the face on the opposite side
for edge in face.edges:
if edge.verts[0] not in intersectionVerts and classifyDict[edge.verts[0]] == IN_FRONT:
if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == IN_FRONT or classifyDict[edge.verts[1]] == ON:
if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge)
elif edge.verts[0] not in intersectionVerts and classifyDict[edge.verts[0]] == BEHIND:
if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == BEHIND or classifyDict[edge.verts[1]] == ON:
if not edge in frontFaceEdgeRemoveList: frontFaceEdgeRemoveList.append(edge)
elif edge.verts[0] in intersectionVerts or classifyDict[edge.verts[0]] == ON:
if edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == IN_FRONT:
if not edge in backFaceEdgeRemoveList: backFaceEdgeRemoveList.append(edge)
elif edge.verts[1] in intersectionVerts or classifyDict[edge.verts[1]] == BEHIND:
if not edge in frontFaceEdgeRemoveList: frontFaceEdgeRemoveList.append(edge)
frontFace.remove(verts = frontFaceVertsRemoveList, edges = frontFaceEdgeRemoveList)
backFace.remove(verts = backFaceVertsRemoveList, edges = backFaceEdgeRemoveList)
#Make an intersection edge
if len(intersectionVerts) < 2:
if DEBUG: face.materialName = 'DEBUG'
return (face, face) # for now
#raise "SplitFaceFace: Too few intersection points!"
elif len(intersectionVerts) > 2:
if DEBUG: face.materialName = 'DEBUG'
return (face, face) # for now
intersectionEdge = Edge(intersectionVerts,face.mesh)
if otherFace.facing == BACK_FACING:
intersectionEdge.CUTISVISIBLE = True #If the cut edge is visible to the camera, mark it so a cutaway would be visible
#Figure out where to insert the edge
for myFace in frontFace, backFace:
for edge in copy(myFace.edges):
if len(intersect(edge.verts, intersectionVerts)) == 1:
myFace.insert((myFace.edges.index(edge) + 1), edges = [intersectionEdge])
break
if len(frontFace.verts) != len(frontFace.edges):
if DEBUG: face.materialName = 'DEBUG'
return (frontFace, face) # for now
#raise IndexError, "The number of verts(%i) does not match the number of edges(%i)" % (len(frontFace.verts), len(frontFace.edges))
if len(backFace.verts) != len(backFace.edges):
if DEBUG: face.materialName = 'DEBUG'
return (face, backFace) # for now
#raise IndexError, "The number of verts(%i) does not match the number of edges(%i)" % (len(backFace.verts), len(backFace.edges))
frontFace.bbox = BoundingBox(frontFace.verts)
backFace.bbox = BoundingBox(backFace.verts)
global SPLIT_CALLS
SPLIT_CALLS += 1
if otherFace.facing == FRONT_FACING:
myFrontFace = frontFace
myBackFace = backFace
elif otherFace.facing == BACK_FACING:
myFrontFace = backFace
myBackFace = frontFace
return (myFrontFace, myBackFace)
def splitCurveFace(curve, otherFace):
"""
If intersecting, returns a tuple of (back curveList, on curveList, front curveList).
"""
classifyDict = dict()
[classifyDict.__setitem__(vert, classifyPointPlane(vert.loc, otherFace.plane)) for vert in curve.verts]
front=False
back=False
for vert in curve.verts:
if front and back:
break #we're already straddling the plane
elif classifyDict[vert]==IN_FRONT:
front=True
elif classifyDict[vert]==BEHIND:
back=True
elif classifyDict[vert]==ON:
pass
if not front and not back:
return ON
elif front and back:
pass
elif (front and otherFace.facing == FRONT_FACING) or (back and otherFace.facing == BACK_FACING):
return IN_FRONT
elif (back and otherFace.facing == FRONT_FACING) or (front and otherFace.facing == BACK_FACING):
return BEHIND
#we have a split, so we need to discover where the actual intersection(s) are/is
edgeDict = dict()
frontCurveList = []
backCurveList = []
onCurveList = []
#set the flag on the first vert
flag = classifyDict[curve.verts[0]]
mySegment = Curve(curve.mesh, [curve.verts[0]], curve.materialName)
#check each vert against the flag - if there is no change, append the vert and edge to mySegment.
#If there is a change, append a copy of mySegment to the proper list and make a new mySegment
for (lastVert, vert, edge) in zip(curve.verts[:-1], curve.verts[1:], curve.edges):
if flag == IN_FRONT:
if classifyDict[vert] == BEHIND:
if classifyDict[lastVert] == IN_FRONT:
splitTuple = splitEdgeFace(edge, otherFace, classifyDict)
if splitTuple:
try: #we have a normal split, resulting in two distinct edges
(myFrontEdge, myBackEdge) = splitTuple
myIntersectionPt = intersect(myFrontEdge.verts, myBackEdge.verts)
mySegment.append(verts = myIntersectionPt, edges = [myFrontEdge])
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt[0], vert], curve.materialName)
mySegment.append(edges = [myBackEdge])
except: #we split on a vert, resulting in only one edge
if splitTuple[0]: #our split results in only a front edge
myIntersectionPt = splitTuple[2]
mySegment.append(verts = myIntersectionPt, edges = splitTuple[0])
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
else: #our split results in only a back edge
myIntersectionPt = splitTuple[2]
mySegment.append(verts = myIntersectionPt, edges = splitTuple[1])
backCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
else:
mySegment.append(verts = [vert], edges = [edge])
elif classifyDict[lastVert] == ON: ###isn't this belt + suspenders?
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [], curve.materialName)
mySegment.append(verts = [vert], edges = [edge])
flag = BEHIND
elif classifyDict[vert] == IN_FRONT or classifyDict[vert] == ON:
mySegment.append(verts = [vert], edges = [edge])
elif flag == BEHIND:
if classifyDict[vert] == IN_FRONT:
if classifyDict[lastVert] == BEHIND:
splitTuple = splitEdgeFace(edge, otherFace, classifyDict)
if splitTuple:
try: #we have a normal split, resulting in two distinct edges
(myFrontEdge, myBackEdge) = splitTuple
myIntersectionPt = intersect(myFrontEdge.verts, myBackEdge.verts)
mySegment.append(verts = myIntersectionPt, edges = [myBackEdge])
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt[0], vert], curve.materialName)
mySegment.append(edges = [myFrontEdge])
except: #we split on a vert, resulting in only one edge
myIntersectionPt = splitTuple[2]
if splitTuple[0]: #our split results in only a front edge
mySegment.append(verts = myIntersectionPt, edges = splitTuple[0])
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
elif splitTuple[0]: #our split results in only a back edge
mySegment.append(verts = myIntersectionPt, edges = splitTuple[1])
backCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [myIntersectionPt], curve.materialName)
else:
mySegment.append(verts = [vert], edges = [edge])
elif classifyDict[lastVert] == ON: ###isn't this belt + suspenders?
frontCurveList.append(copy(mySegment))
mySegment = Curve(curve.mesh, [], curve.materialName)
mySegment.append(verts = [vert], edges = [edge])
flag = IN_FRONT
elif classifyDict[vert] == BEHIND or classifyDict[vert] == ON:
mySegment.append(verts = [vert], edges = [edge])
elif flag == ON:
if classifyDict[vert] == IN_FRONT:
mySegment.append(verts = [vert], edges = [edge])
flag = IN_FRONT
elif classifyDict[vert] == BEHIND:
mySegment.append(verts = [vert], edges = [edge])
flag = BEHIND
else:
mySegment.append(verts = [vert], edges = [edge])
if flag == IN_FRONT:
frontCurveList.append(copy(mySegment))
elif flag == BEHIND:
backCurveList.append(copy(mySegment))
elif flag == ON:
onCurveList.append(copy(mySegment))
if otherFace.facing == FRONT_FACING:
return (backCurveList, onCurveList, frontCurveList)
else:
return (frontCurveList, onCurveList, backCurveList)
def segmentIntersection(edge, plane):
"""
returns the intersection point with the plane on the edge
if one of the verts is on the plane, or there is no intersection,
it returns False.
based on
http://geometryalgorithms.com/Archive/algorithm_0104/algorithm_0104B.htm#Line-Plane%20Intersection
"""
u = edge.verts[1].loc - edge.verts[0].loc
w = edge.verts[0].loc - plane.point
D = plane.normal*u
N = -plane.normal*w
if abs(D) < EPSILON3D:
return False #edge is parallel to the plane
sI=N / D
if sI < 0 or sI > 1:
return False #intersection point not on edge
intersectionPt=Vert(edge.scene, loc = edge.verts[0].loc + (sI * u))
return intersectionPt
def splitEdgeFace(edge, face, classifyDict):
"""
Splits an edge (or segment) by a face.
classifyDict is a dict with the classifications of the verts according to the splitting face.
If there is a normal intersection, returns a tuple of (frontEdge, backEdge)
If the intersection is ON one of the points, returns a tuple (frontEdge, intersectionPt) or (intersectionPt, backEdge)
if there is no intersection, returns (None, None)
"""
intersectionPt = segmentIntersection(edge, face.plane)
if not intersectionPt: return False #no intersection or the edge is parallel to the face
if intersectionPt == edge.verts[0]: #the intersectionPt is one of our endpoints
classifyDict[edge.verts[0]] = ON
if classifyDict[edge.verts[1]] == IN_FRONT:
return (edge, None, intersectionPt)
else:
return (None, edge, intersectionPt)
elif intersectionPt == edge.verts[1]:
classifyDict[edge.verts[1]] = ON
if classifyDict[edge.verts[0]] == IN_FRONT:
return (edge, None, intersectionPt)
else:
return (None, edge, intersectionPt)
#We must have a proper intersection, with two resulting segments:
if classifyPointPlane(edge.verts[0].loc, face.plane) == IN_FRONT: #the first vert is in front, so the first segment is in front
frontVert = edge.verts[0]
backVert = edge.verts[1]
else:
frontVert = edge.verts[1]
backVert = edge.verts[0]
frontEdge = copy(edge)
frontEdge.verts = [frontVert, intersectionPt]
backEdge = copy(edge)
backEdge.verts = [intersectionPt,backVert]
intersectionPt.edges = set([frontEdge, backEdge])
#figure out where to insert the intersection point in adjoining faces
for myFace in edge.faces:
firstVertIndex = myFace.verts.index(edge.verts[0])
secondVertIndex = myFace.verts.index(edge.verts[1])
if abs(firstVertIndex - secondVertIndex) > 1:
myFace.append(verts = [intersectionPt])
else:
myFace.insert(max(firstVertIndex,secondVertIndex),verts = [intersectionPt])
return (frontEdge,backEdge)
#---3D GEOMETRY CLASSES---#000000#FFFFFF-----------------------------
class BoundingBox:
def __init__(self,vertList):
"""
computes a bounding sphere for the object
"""
self.xMin = self.xMax = self.yMin = self.yMax = self.zMin = self.zMax = NOT_SET
try: #assume we have a 3d object with Vec3 locations
for vert in vertList:
if self.zMin>vert.loc.z or self.zMin is NOT_SET:self.zMin=vert.loc.z
if self.zMax<vert.loc.z or self.zMax is NOT_SET:self.zMax=vert.loc.z
if self.xMin>vert.loc.x or self.xMin is NOT_SET:self.xMin=vert.loc.x
if self.xMax<vert.loc.x or self.xMax is NOT_SET:self.xMax=vert.loc.x
if self.yMin>vert.loc.y or self.yMin is NOT_SET:self.yMin=vert.loc.y
if self.yMax<vert.loc.y or self.yMax is NOT_SET:self.yMax=vert.loc.y
#Joggle the bounds to avoid flat bboxes
if abs(self.xMin - self.xMax) < EPSILON3D:
self.xMin -= EPSILON3D
self.xMax += EPSILON3D
if abs(self.yMin - self.yMax) < EPSILON3D:
self.yMin -= EPSILON3D
self.yMax += EPSILON3D
if abs(self.zMin - self.zMax) < EPSILON3D:
self.zMin -= EPSILON3D
self.zMax += EPSILON3D
except: #No? Must be 2d then
for vert in vertList:
if self.xMin>vert.loc2d.x or self.xMin is NOT_SET:self.xMin=vert.loc2d.x
if self.xMax<vert.loc2d.x or self.xMax is NOT_SET:self.xMax=vert.loc2d.x
if self.yMin>vert.loc2d.y or self.yMin is NOT_SET:self.yMin=vert.loc2d.y
if self.yMax<vert.loc2d.y or self.yMax is NOT_SET:self.yMax=vert.loc2d.y
#Joggle the bounds to avoid flat bboxes
if abs(self.xMin - self.xMax) < EPSILON2D:
self.xMin -= EPSILON2D
self.xMax += EPSILON2D
if abs(self.yMin - self.yMax) < EPSILON2D:
self.yMin -= EPSILON2D
self.yMax += EPSILON2D
def doesIntersect(self,otherBbox):
if (((self.xMin<otherBbox.xMin<self.xMax) or (self.xMin<otherBbox.xMax<self.xMax) or (otherBbox.xMin<self.xMin<otherBbox.xMax) or (otherBbox.xMin<self.xMax<otherBbox.xMax))
and ((self.yMin<otherBbox.yMin<self.yMax) or (self.yMin<otherBbox.yMax<self.yMax) or (otherBbox.yMin<self.yMin<otherBbox.yMax) or (otherBbox.yMin<self.yMax<otherBbox.yMax))
and ((self.zMin<otherBbox.zMin<self.zMax) or (self.zMin<otherBbox.zMax<self.zMax) or (otherBbox.zMin<self.zMin<otherBbox.zMax) or (otherBbox.zMin<self.zMax<otherBbox.zMax))):
return True
else:
return False
class Plane:
def __init__(self,normal,point):
self.normal=normal.normalize()
self.point=point
#self.point = MidpointVecs(MidpointVecs(pts[0], pts[1]), pts[2])
## self.A = self.normal.x
## self.B = self.normal.y
## self.C = self.normal.z
## self.D = -(pts[0].x * (pts[1].y * pts[2].z - pts[2].y * pts[1].z) + pts[1].x * (pts[2].y * pts[0].z - pts[0].y * pts[2].z) + pts[2].x * (pts[0].y * pts[1].z - pts[1].y * pts[0].z))
class Scene:
def __init__(self):
self.meshes = set()
self.faces = set()
self.curves = set()
self.verts = set()
self.cam2ClipTransform = False
self.projection = False
self.BSPTree = False
def getPerspMatrix(self, fovx, aspect, near, far, xOffset=0, yOffset=0):
near = 0.5
right = tan(radians(fovx * 0.5)) * near
left = -right
top = float(right / aspect)
bottom = -top
#OpenGL
return Matrix( [(2.0 * near)/float(right - left), 0.0, float(right + left)/float(right - left), 0.0],
[0.0, (2.0 * near)/float(top - bottom), float(top + bottom)/float(top - bottom), 0.0],
[0.0, 0.0, -float(far + near)/float(far - near), -(2.0 * far * near)/float(far - near)],
[0.0, 0.0, -1.0, 0.0])
#Regular (-1 -> 1 Z-depth)
## return Matrix( [(2.0 * near)/(right - left), 0.0, -float(right + left)/(right - left), 0.0],
## [0.0, (2.0 * near)/(top - bottom), -float(top + bottom)/(top - bottom), 0.0],
## [0.0, 0.0, float(far + near)/(far - near), -(2.0 * far * near)/(far - near)],
## [0.0, 0.0, 1.0, 0.0]) * Matrix([1,0,0,0],[0,1,0,0],[0,0,-1,0],[0,0,0,1])
#DirectX
## return Matrix( [(2.0 * near)/float(right - left), 0.0, -float(right + left)/float(right - left), 0.0],
## [0.0, (2.0 * near)/float(top - bottom), -float(top + bottom)/float(top - bottom), 0.0],
## [0.0, 0.0, float(far)/float(far - near), -(far * near)/float(far - near)],
## [0.0, 0.0, 1.0, 0.0])
def getOrthoMatrix(self, aspect , near, far, scale, xOffset=0, yOffset=0):
top = float(scale/2/aspect)
bottom = -top
left = -float(scale/2)
right = -left
near = .001
r_l = float(right - left)
t_b = float(top - bottom)
f_n = float(far - near)
#DirectX (z = 0->1)
return Matrix( [2.0/float(right - left), 0.0, 0.0, -float(right + left)/float(right - left)],
[0.0, 2.0/float(top - bottom), 0.0, -float(top + bottom)/float(top - bottom)],
[0.0, 0.0, 1.0/float(far - near), -float(near)/float(far - near)],
[0.0, 0.0, 0.0, 1.0])
## return Matrix( [2.0/float(right - left), 0.0, 0.0, -float(right + left)/float(right - left)],
## [0.0, 2.0/float(top - bottom), 0.0, -float(top + bottom)/float(top - bottom)],
## [0.0, 0.0, 2.0/float(far - near), -float(far + near)/float(far - near)],
## [0.0, 0.0, 0.0, -1.0])
class Mesh:
def __init__(self,scene,name):
self.scene = scene
self.name = name
self.scene.meshes.add(self)
self.type = GEOMETRY_TYPE
self.creaseAngle = 30 #this value gets set according to the Blender's "Auto smooth" value
self.layers = None
self.edges = set()
self.faces = set()
self.curves = list()
class Face:
def __init__(self,mesh,vertList, materialName = None, normal = False, facing = False, type = GEOMETRY_TYPE):
self.type = type
self.scene = mesh.scene
self.mesh = mesh
self.verts = vertList
self.materialName = materialName
self.normal = normal
self.facing = facing
#check for the minimum number of verts
if len(vertList) < 3:
raise ValueError, "ERROR - cannot define a face with less than three vertices!"
#these attributes are user-accessible
self.VISIBLE = True
#these all need to be set eventually
self.bbox = False
self.edges = []
#begin to build connectivity data
[vert.faces.add(self) for vert in self.verts]
self.scene.faces.add(self)
self.mesh.faces.add(self)
def __copy__(self):
"""
Returns a copy of the face
"""
newFace = Face(self.mesh, copy(self.verts), self.materialName, self.normal, self.facing)
newFace.edges = copy(self.edges)
newFace.VISIBLE = self.VISIBLE
newFace.type = self.type
newFace.plane = self.plane
#insert the copied face into the mesh topology
[vert.faces.add(newFace) for vert in self.verts]
[edge.faces.append(newFace) for edge in self.edges]
return newFace
def isFace(self):
#silly type-checking!
return True
def destroy(self):
"""
Removes face from mesh and scene
"""
[edge.faces.remove(self) for edge in self.edges]
[vert.faces.remove(self) for vert in self.verts]
self.scene.faces.remove(self)
self.mesh.faces.remove(self)
def append(self, verts = [], edges = []):
for vert in verts:
self.verts.append(vert)
vert.faces.add(self)
for edge in edges:
self.edges.append(edge)
edge.faces.append(self)
def insert(self, index, verts = [], edges = []):
"""
Inserts verts or edges into the face
"""
myVerts = copy(verts)
myVerts.reverse()
for vert in myVerts:
if vert in self.verts: raise "Trying to add a vert that is already present!"
self.verts.insert(index,vert)
vert.faces.add(self)
myEdges = copy(edges)
myEdges.reverse()
for edge in myEdges:
if edge in self.edges: raise "Trying to add an edge that is already present!"
self.edges.insert(index,edge)
edge.faces.append(self)
def remove(self, verts = [], edges = []):
"""
Removes verts or edges from face
"""
for vert in verts:
self.verts.remove(vert)
vert.faces.remove(self)
for edge in edges:
self.edges.remove(edge)
edge.faces.remove(self)
class Curve:
#A curve in pantograph is a sequence of verts and straight segments (edges)
def __init__(self, mesh, vertList, materialName = None):
self.mesh = mesh
self.scene = mesh.scene
self.verts = vertList
self.materialName = materialName
#these attributes are are user-accessible
self.VISIBLE = True
#these all need to be set eventually
self.edges = []
#begin to build connectivity data
[vert.curves.add(self) for vert in self.verts]
self.scene.curves.add(self)
self.mesh.curves.append(self)
def __copy__(self):
"""
Returns a copy of the curve
"""
newCurve = Curve(self.mesh, copy(self.verts), self.materialName)
newCurve.edges = copy(self.edges)
newCurve.VISIBLE = self.VISIBLE
#insert the copied face into the mesh topology
[vert.curves.add(newCurve) for vert in self.verts]
return newCurve
def destroy(self):
"""
Removes curve from scene
"""
[vert.curves.remove(self) for vert in self.verts]
self.scene.curves.remove(self)
self.mesh.curves.remove(self)
def isFace(self):
#silly type-checking!
return False
def append(self, verts = [], edges = []):
for vert in verts:
self.verts.append(vert)
vert.curves.add(self)
for edge in edges:
self.edges.append(edge)
def insert(self, index, verts = [], edges = []):
"""
Inserts verts or edges into the curve
"""
myVerts = copy(verts)
myVerts.reverse()
for vert in myVerts:
if vert in self.verts: raise "Trying to add a vert that is already present!"
self.verts.insert(index,vert)
vert.curves.add(self)
myEdges = copy(edges)
myEdges.reverse()
for edge in myEdges:
if edge in self.edges: raise "Trying to add an edge that is already present!"
self.edges.insert(index,edge)
def remove(self, verts = [], edges = []):
"""
Removes verts or edges from curve
"""
for vert in verts:
self.verts.remove(vert)
vert.curves.remove(self)
for edge in edges:
self.edges.remove(edge)
class Edge:
def __init__(self,vertList,mesh):
self.mesh = mesh
self.scene = mesh.scene
self.verts = list(vertList)
self.bbox = BoundingBox(self.verts)
#check for the minimum number of verts
if len(vertList)<2:
raise ValueError, "ERROR - cannot define an edge with less than two vertices!"
#attributes
self.SHARP = False #Determines whether the edge will become a crease or not
self.MESH = False #The edge is an original poly edge, before trinagulation
self.HIDDEN = False #The edge has been occluded
self.SILHOUETTE = False #Silhouette edge, between front-facing and back-facing polygons
self.DRAWN = False #Flag that indicates that the edge has been drawn
self.CLIPPED = False #Indicates the edge has been visited by the 2d clipping algorithm
self.VISIBLE = True ## Why is this needed in addition to HIDDEN? Its confusing...
self.INTERSECTION = False ## This feature doesn't exist yet!!
self.CUT = False #An edge that has been clipped by a Cutaway or Vignette
self.CUTISVISIBLE = False #determines whether a cut edge should be shown as a Cut or Silhouette
#these are additional topology nodes which are set during polyline-making
#self.hardConnections = set()
#self.softConnections = set()
#these all need to be set eventually
self.faces = list()
#begin to build connectivity data
[vert.edges.add(self) for vert in self.verts]
self.mesh.edges.add(self)
def __copy__(self):
"""
Returns a copy of the edge
"""
newEdge = Edge(copy(self.verts),self.mesh)
newEdge.faces = copy(self.faces)
newEdge.SHARP = self.SHARP
newEdge.MESH = self.MESH
newEdge.HIDDEN = self.HIDDEN
newEdge.SILHOUETTE = self.SILHOUETTE
newEdge.DRAWN = self.DRAWN
newEdge.VISIBLE = self.VISIBLE
newEdge.INTERSECTION = self.INTERSECTION
newEdge.CUT = self.CUT
[vert.edges.add(newEdge) for vert in self.verts]
[face.edges.append(newEdge) for face in self.faces]
return newEdge
def matchProperties(self, newEdge):
"""
Matches the other edge's properties to the edge
"""
newEdge.SHARP = self.SHARP
newEdge.MESH = self.MESH
newEdge.HIDDEN = self.HIDDEN
newEdge.SILHOUETTE = self.SILHOUETTE
newEdge.DRAWN = self.DRAWN
newEdge.VISIBLE = self.VISIBLE
newEdge.INTERSECTION = self.INTERSECTION
newEdge.CUT = self.CUT
def destroy(self):
[face.edges.remove(self) for face in self.faces]
[vert.edges.remove(self) for vert in self.verts]
self.mesh.edges.remove(self)
def collapse(self):
"""
removes the second vert on the edge, collapsing all connections
onto the first vert
"""
debug('COLLAPSED!')
keepVert = self.verts[0]
destroyVert = self.verts[1]
#add the destroyed vert's connections to the kept vertice
keepVert.edges = keepVert.edges.union(destroyVert.edges)
keepVert.faces = keepVert.faces.union(destroyVert.faces)
#add the kept vertice to the destroyed vert's connections and remove the destroyed vert
for edge in destroyVert.edges:
if not keepVert in edge.verts:
edge.verts.append(keepVert)
try:
edge.verts.remove(destroyVert)
except:
if len(edge.verts != 2): raise "Oops!"
for face in destroyVert.faces:
if not keepVert in face.verts:
face.verts.insert(face.verts.index(destroyVert), keepVert)
face.verts.remove(destroyVert)
#remove the deleted vertice from the mesh and the scene
self.scene.verts.remove(destroyVert)
#remove the edge
[face.edges.remove(self) for face in self.faces]
[vert.edges.remove(self) for vert in self.verts]
self.mesh.edges.remove(self)
def append(self, verts = []):
"""
Appends verts to an edge
"""
for vert in verts:
self.verts.append(vert)
vert.edges.add(self)
if len(self.verts) > 2:
debug(self.verts)
raise ValueError, "Cannot have more than two verts in an edge!"
def remove(self, verts = []):
"""
Removes verts from edge
"""
for vert in verts:
self.verts.remove(vert)
vert.edges.remove(self)
class Vert:
def __init__(self, scene, loc = None, loc2d = None):
self.scene = scene
self.loc = loc
self.loc2d = loc2d
#these all need to be set eventually
self.edges = set()
self.faces = set()
self.curves = set()
#these will be used to track sharp or soft corners, so Regions can have the same softness as Polylines
self.SHARP = True #Default is sharp: new verts with unknown softness are sharp until verified
self.SOFT = False
self.scene.verts.add(self)
def __hash__(self):
return self.loc.__hash__()
def compareLoc2d(self, otherLoc2d):
"""
compares an unstructured 2d location [x,y] with the vert's loc2d
returns True if the difference is less than EPSILON2D
"""
if ((abs(self.loc2d.x - otherLoc2d[0]) < EPSILON2D) and (abs(self.loc2d.y - otherLoc2d[1]) < EPSILON2D)):
return self
else:
return False
class BSPTree:
def __init__(self,faceList, curveList):
global PROCESSED_FACES
global TOTAL_FACES
self.tree = [None, None, None]
self.root = self.tree[0]
self.front = self.tree[1]
self.back = self.tree[2]
if len(faceList) > 1:
self.makeNewBranch(faceList, curveList)
else:
self.root = faceList + curveList
def backToFront(self, depth):
global MAX_DEPTH
myList = []
if self.back:
myList.extend(self.back.backToFront(depth + 1))
myList.extend(self.root)
if depth > MAX_DEPTH: MAX_DEPTH = depth
if self.front:
myList.extend(self.front.backToFront(depth + 1))
return myList
def makeNewBranch(self, faceList, curveList):
print ".",
global TOTAL_FACES
if not faceList: #this case handles places where we have curves but no splitting faces
self.root = curveList
else:
numTests = 5
if numTests > len(faceList): numTests = len(faceList)
bestScore = False
bestNode = False
classifyDict = dict()
candidateList = []
frontList = []
backList = []
onList = []
intersectList = []
degenerate = True
#select a face for the splitting face
### Here we could add in a test for degenerate (spherical) meshes!
for test in range(numTests):
candidate = faceList[test]
myFrontList = []
myBackList = []
myOnList = []
myIntersectList = []
#make a dict with the classifications of all the verts according to the splitting face
myClassifyDict = dict()
#classify all the faces according to the splitting face (setting classifyDict in the process)
classifyList = [classifyFaceFace(face, candidate, myClassifyDict) for face in faceList[:test] + faceList[test+1:]]
myFrontList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == IN_FRONT]
myBackList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == BEHIND]
myOnList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == ON]
myIntersectList = [face for (classify,face) in zip(classifyList,faceList[:test] + faceList[test+1:]) if classify == INTERSECTING]
myScore = len(myIntersectList)
#If all of the faces end up on one side or the other, we have a degenerate condition
if (len(classifyList) < RECURSION_LIMIT) or (myFrontList and myBackList) :
degenerate = False
if not bestNode or (myScore < bestScore):
bestScore = myScore
bestNode = candidate
frontList = myFrontList
backList = myBackList
intersectList = myIntersectList
onList = myOnList
classifyDict = myClassifyDict
if not degenerate:
self.root = [bestNode]
else:
### replace this with a true solution to the degeneracy
self.root = [bestNode]#raise("We have a degenerate condition developing!")
#put the lists in the appropriate branch of the tree
if intersectList:
##!!! We can't handle exceptions here! !!!
splitList = [splitFaceFace(face, self.root[0], classifyDict) for face in intersectList]
for (split,face) in zip(splitList,intersectList):
if split == IN_FRONT:
frontList.append(face)
elif split == BEHIND:
backList.append(face)
elif split == ON:
onList.append(face)
elif split:
frontList.append(split[0])
backList.append(split[1])
TOTAL_FACES += 1
##!!! What would cause this final case? !!!
else:
debug("*******We have an incorrect split!********")
backList.append(face)
#split the curves in curveList by the root node
frontCurveList = []
backCurveList = []
onCurveList = []
if curveList:
curveSplitList = [splitCurveFace(curve, self.root[0]) for curve in curveList]
for (split, curve) in zip(curveSplitList, curveList):
if split == IN_FRONT:
frontCurveList.append(curve)
elif split == BEHIND:
backCurveList.append(curve)
elif split == ON:
onCurveList.append(curve)
elif split:
frontCurveList.extend(split[2])
onCurveList.extend(split[1])
backCurveList.extend(split[0])
#add the faces and curves to the proper branches of the tree
self.root.extend(onList)
self.root.extend(onCurveList)
if frontList or frontCurveList:
self.front = BSPTree(frontList, frontCurveList)
if backList or backCurveList:
self.back = BSPTree(backList, backCurveList)
#---2D GEOMETRY FUNCTIONS---#000000#FFFFFF--------------------------------------
def isOnSegment(vert, segment):
'''
determines if a location(vector) is on given edge
from http://www.geometryalgorithms.com/Archive/algorithm_0102/algorithm_0102.htm
'''
v = segment[1].loc2d - segment[0].loc2d
w = vert.loc2d - segment[0].loc2d
c1 = w * v
if c1 <= EPSILON2D:
if distance(vert.loc2d, segment[0].loc2d) < EPSILON2D: return True
else: return False
c2 = v * v
if c2 <= c1:
if distance(vert.loc2d, segment[1].loc2d) < EPSILON2D: return True
else: return False
b = c1 / c2
pointB = segment[0].loc2d + (b * v)
if distance(vert.loc2d, pointB) < EPSILON2D: return True
else: return False
def perpendicularProduct(u, v):
"""
returns the 2d perpendicular product of two vectors
"""
return ((u).x * (v).y - (u).y * (v).x)
def intersectEdgeSegment(edge, segment):
"""
returns a tuple (mu, intersection point)
see http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/example.cpp
"""
#first, discard cases where intersection is impossible
if ((edge.verts[0].loc2d.x < segment[0][0]) and (edge.verts[0].loc2d.x < segment[1][0]) and (edge.verts[1].loc2d.x < segment[0][0]) and (edge.verts[1].loc2d.x < segment[1][0])):
return False
if ((edge.verts[0].loc2d.y < segment[0][1]) and (edge.verts[0].loc2d.y < segment[1][1]) and (edge.verts[1].loc2d.y < segment[0][1]) and (edge.verts[1].loc2d.y < segment[1][1])):
return False
if ((edge.verts[0].loc2d.x > segment[0][0]) and (edge.verts[0].loc2d.x > segment[1][0]) and (edge.verts[1].loc2d.x > segment[0][0]) and (edge.verts[1].loc2d.x > segment[1][0])):
return False
if ((edge.verts[0].loc2d.y > segment[0][1]) and (edge.verts[0].loc2d.y > segment[1][1]) and (edge.verts[1].loc2d.y > segment[0][1]) and (edge.verts[1].loc2d.y > segment[1][1])):
return False
#proceed with finding an intersection pt
denominator = ((segment[1][1] - segment[0][1]) * (edge.verts[1].loc2d.x - edge.verts[0].loc2d.x)) - \
((segment[1][0] - segment[0][0]) * (edge.verts[1].loc2d.y - edge.verts[0].loc2d.y))
numeratorA = ((segment[1][0] - segment[0][0]) * (edge.verts[0].loc2d.y - segment[0][1])) - \
((segment[1][1] - segment[0][1]) * (edge.verts[0].loc2d.x - segment[0][0]))
numeratorB = ((edge.verts[1].loc2d.x - edge.verts[0].loc2d.x) * (edge.verts[0].loc2d.y - segment[0][1])) - \
((edge.verts[1].loc2d.y - edge.verts[0].loc2d.y) * (edge.verts[0].loc2d.x - segment[0][0]))
if (denominator == 0):
return False #PARALLEL OR COINCIDENT
ua = numeratorA / denominator
ub = numeratorB / denominator
if ((ua >= 0.0) and (ua <= 1.0) and (ub >= 0.0) and (ub <= 1.0)):
#get the intersection point
intersectionPoint = Vector(0,0)
intersectionPoint.x = edge.verts[0].loc2d.x + ua*(edge.verts[1].loc2d.x - edge.verts[0].loc2d.x)
intersectionPoint.y = edge.verts[0].loc2d.y + ua*(edge.verts[1].loc2d.y - edge.verts[0].loc2d.y)
#ua should be the mu value for the intersection point
return (ua, intersectionPoint) #INTERSECTING
return False #NOT_INTERSECTING - the intersection point is not on the line
def clipEdgeRegion(edge, region):
"""
clips edge against a region, returning a tuple of edge-segments, marked as Hidden where appropriate
"""
#in the case of an empty region, just return the edge itself
if len(region.polygon)<1:
return [edge]
#check if the edge's verts are entirely inside the region
maybeClipped = False
if region.polygon.isInside(edge.verts[0].loc2d.x, edge.verts[0].loc2d.y) and region.polygon.isInside(edge.verts[1].loc2d.x, edge.verts[1].loc2d.y):
maybeClipped = True
#get a list of segments in the region, discarding any whose bounding boxes don't overlap the edge's
segmentList = [(point, nextPoint) for contour in region.polygon for (point, nextPoint) in zip(contour, (contour[1:] + contour[:1]))]
if not segmentList:
return [edge] #if we have an edge with no intersections at all, we just return the edge
#get the intersection points of the list and the edge, and rank them by mu (aka decorate and sort)
intersectPoints = [intersectEdgeSegment(edge, segment) for segment in segmentList]
intersectPoints = filter(None, intersectPoints) #discard non-intersections by removing False results
if not intersectPoints:
if maybeClipped:
edge.HIDDEN = True #with no intersections, and both verts inside, it must be hidden
return [edge] #if we have an edge with no intersections at all, we just return the edge
else:
intersectPoints.sort() #sort the intersection points by their parametric distance along the edge
#make new verts, and put them in a list
vertList = [Vert(edge.scene, loc2d = loc) for (index, loc) in intersectPoints]
vertList.insert(0,edge.verts[0])
vertList.append(edge.verts[1])
edgeList = list()
[edgeList.append(Edge([vert, nextVert], edge.mesh)) for (vert, nextVert) in zip(vertList[:-1], vertList[1:])]
#duplicate flags from the original edge in the new edges
[edge.matchProperties(newEdge) for newEdge in edgeList]
#figure out if the first point is inside the region's polygon
#check a point mid-way between the first and second verts - this gets around Polygon's uncertainty about boundary points
#midwayPoint = MidpointVecs(edge.verts[0].loc2d, edge.verts[1].loc2d)
if region.isInside(vertList[0].loc2d):#midwayPoint):
isInside = True
else:
isInside = False
#mark the new edges according to Hidden or not
for newEdge in edgeList:
if isInside:
newEdge.HIDDEN = True
else:
newEdge.HIDDEN = False
isInside = not isInside
#destroy original edge
edge.destroy()
#return the list of new edges
return edgeList
def combinePolylines(drawing, polylineDict):
"""
combine single segments into longer polylines
myPolylineDict is a dictionary by (linetype, list of segments)
"""
for key in polylineDict: #We know that these edges are all of the same type...
if (key != 'MESH'): #no MESH edges are connected
edgeSet = set(polylineDict[key])
polylineList = list()
while edgeSet:
myEdge = edgeSet.pop()
myPolyline = Polyline([myEdge])
#here we extend the polyline as far as possible in one direction, then flip the list, and extend in the other direction
if edgeSet: #if we have more segments, try to add them
currentVert = nextVert = myPolyline.verts[-1]
if nextVert:
nextVert = None
for edge in currentVert.edges:
if edge in edgeSet:
edgeSet.remove(edge)
nextVert = [vert for vert in edge.verts if vert != currentVert][0]
myPolyline.append(vertList = [nextVert])
currentVert = nextVert
break
myPolyline.verts.reverse()
currentVert = nextVert = myPolyline.verts[-1]
if nextVert:
nextVert = None
for edge in currentVert.edges:
if edge in edgeSet:
edgeSet.remove(edge)
nextVert = [vert for vert in edge.verts if vert != currentVert][0]
myPolyline.append(vertList = [nextVert])
currentVert = nextVert
break
myPolyline.verts.reverse()
polylineList.append(myPolyline)
else: #we need to make one-segment polylines out of the individual MESH edges
polylineList = [Polyline([edge]) for edge in polylineDict[key]]
polylineDict[key] = polylineList
def clipObjects(drawing, object, myPolylineDict, isHidden):
"""
determine if the object is a face or a curve,
clip the object against the appropriate clipping regions,
and classify the fills and segments into the appropriate dicts
"""
global CLIP_COUNTER
if CLIP_COUNTER%100 == 0:
print ".",
CLIP_COUNTER+=1
scene = drawing.scene
#DETERMINE IF WE HAVE A FACE OR A CURVE
if object.isFace():#if we have a regular face with edges
myRegion = Region(scene, vertList = object.verts)
#Clip myRegion and its edges against the clipping region if necessary:
#face covered: all edges are hidden
if isHidden:
for edge in object.edges:
if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT):
myPolylineDict['HIDDEN'].append( edge )
edge.CLIPPED = True
elif drawing.lineClippingRegion.covers(myRegion):
for edge in object.edges:
if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT):
myPolylineDict['HIDDEN'].append( edge )
edge.CLIPPED = True
#face partially covered: clip the edges
elif drawing.lineClippingRegion.overlaps( myRegion ):
myEdgeList = copy(object.edges)#otherwise, we screw things up when we add split edges to object.edges
for edge in myEdgeList:
if (not edge.CLIPPED) and (edge.SILHOUETTE or edge.SHARP or edge.CUT or edge.MESH):
myClippedList = clipEdgeRegion(edge, drawing.lineClippingRegion)
for newEdge in myClippedList:
if newEdge.HIDDEN and (newEdge.SILHOUETTE or newEdge.SHARP or newEdge.CUT): myPolylineDict['HIDDEN'].append( newEdge )
elif newEdge.SILHOUETTE: myPolylineDict['SILHOUETTE'].append( newEdge )
elif newEdge.SHARP: myPolylineDict['CREASE'].append( newEdge )
elif newEdge.CUT: myPolylineDict['CUT'].append( newEdge )
elif not newEdge.HIDDEN and newEdge.MESH: myPolylineDict['MESH'].append( newEdge )
edge.CLIPPED = True
#neither face nor its edges are clipped: both are added to the drawing queue
else:
for edge in object.edges:
if not edge.CLIPPED:
if edge.SILHOUETTE: myPolylineDict['SILHOUETTE'].append( edge )
elif edge.SHARP: myPolylineDict['CREASE'].append( edge )
elif edge.CUT: myPolylineDict['CUT'].append( edge )
elif edge.MESH: myPolylineDict['MESH'].append( edge )
edge.CLIPPED = True
#clip the regions for fills
if drawing.settings.USE_ALPHA:
if (isHidden or drawing.fillClippingRegion.covers( myRegion )):
myRegion = Region(scene)
elif drawing.fillClippingRegion.overlaps( myRegion ):
myRegion = myRegion - drawing.fillClippingRegion
else:
if (isHidden or drawing.lineClippingRegion.covers( myRegion )):
myRegion = Region(scene)
elif drawing.lineClippingRegion.overlaps( myRegion ):
myRegion = myRegion - drawing.lineClippingRegion
#add myRegion to the proper material Region and to the clippingRegion
if (drawing.settings.USE_ALPHA) and (drawing.settings.pens[object.materialName].attributeDict["FILLCOLOR"].a < 1.0):#we have a transparent fill: transparent fills do not occlude other fills
drawing.transparentFillDict[object.materialName][object.mesh] = (drawing.transparentFillDict[object.materialName][object.mesh] + myRegion)
drawing.lineClippingRegion = drawing.lineClippingRegion + myRegion
if not drawing.currentTransparentRegion: #this is the top-most transparent fill Region
drawing.currentTransparentRegion = [object.materialName, myRegion]
else:#if we need to make a new transparent region, copy the current transparent region into the queue and start a new region
if myRegion.overlaps(drawing.currentTransparentRegion[1]):
drawing.transparentFillQueue.append(copy(drawing.currentTransparentRegion))
drawing.currentTransparentRegion = [object.materialName, myRegion]
elif object.materialName == drawing.currentTransparentRegion[0]:
drawing.currentTransparentRegion[1] = drawing.currentTransparentRegion[1] + myRegion
else:
drawing.transparentFillQueue.append(copy(drawing.currentTransparentRegion))
drawing.currentTransparentRegion = [object.materialName, myRegion]
else:#we have an opaque material
if not drawing.fillDict[object.materialName][object.mesh]: #If we haven't used this Region, it must be initialized
drawing.fillDict[object.materialName][object.mesh] = Region(scene)
drawing.fillDict[object.materialName][object.mesh] = drawing.fillDict[object.materialName][object.mesh] + myRegion
drawing.lineClippingRegion = drawing.lineClippingRegion + myRegion
if drawing.settings.USE_ALPHA:
drawing.fillClippingRegion = drawing.fillClippingRegion + myRegion
else: #we have a curve: clip edges and add them to their appropriate polyline dict
for edge in object.edges:
myClippedList = clipEdgeRegion(edge, drawing.lineClippingRegion)
for newEdge in myClippedList:
if newEdge.HIDDEN:
myPolylineDict['HIDDEN'].append( newEdge )
else:
myPolylineDict['CURVE'].append( newEdge )
#---2D GEOMETRY CLASSES---#000000#FFFFFF--------------------------------------
class Region:
"""
A collection of contours, bounded by verts
A new region can only be initialized with a single (outer) contour
"""
def __init__(self, scene, vertList = []):
self.scene = scene
if vertList:
self.polygon = Polygon([(vert.loc2d.x, vert.loc2d.y) for vert in vertList])
else:
self.polygon = Polygon()
def __add__(self, otherRegion):
newRegion = copy(self)
newRegion.polygon = self.polygon + otherRegion.polygon
return newRegion
def __sub__(self, otherRegion):
newRegion = copy(self)
newRegion.polygon = self.polygon - otherRegion.polygon
return newRegion
def overlaps(self, otherRegion):
if self.polygon.overlaps(otherRegion.polygon):
return True
else:
return False
def covers(self, otherRegion):
if self.polygon.covers(otherRegion.polygon):
return True
else:
return False
def isInside(self, loc):
if self.polygon.isInside(loc.x, loc.y):
return True
else:
return False
def draw(self, context):
for contour in self.polygon:
context.move_to(contour[0][0], contour[0][1])
for point in contour[1:]:
context.line_to(point[0], point[1])
context.close_path()
def drawSWF(self, shape, movie, color):
myShape = shape
myShape.setLine(0,0,0,0,0)
myShape.setRightFill(myShape.addFill(color.r* 255,color.g* 255,color.b* 255,color.a* 255))
for contour in self.polygon:
myShape.movePenTo(contour[0][0], contour[0][1])
for point in contour[1:]:
myShape.drawLineTo(point[0], point[1])
myShape.drawLineTo(contour[0][0], contour[0][1])
return [myShape]
def outline(self, context):
for contour in self.polygon:
context.move_to(contour[0][0], contour[0][1])
for point in contour[1:]:
context.line_to(point[0], point[1])
context.close_path()
def outlineSWF(self, shape, movie, width, color):
myShape = self.shape
myShape.setLine(width, color.r* 255,color.g* 255,color.b* 255,color.a* 255)
for contour in self.polygon:
myShape.movePenTo(contour[0][0], contour[0][1])
for point in contour[1:]:
myShape.drawLineTo(point[0], point[1])
myShape.drawLineTo(contour[0][0], contour[0][1])
return [myShape]
class Polyline:
"""
an ordered list of edges
"""
def __init__(self, edgeList = None, vertList = None):
self.closed = False #tells us whether the polyline is closed or open
self.verts = list()
self.edges = list()
self.edges = edgeList
if len(self.edges) == 1:
self.verts = self.edges[0].verts
else:
for (edge, nextEdge) in zip(self.edges[:-1], self.edges[1:]):
if edge.verts[0] in nextEdge.verts:
self.verts.append[edge.verts[1]]
self.verts.append[edge.verts[0]]
elif edge.verts[1] in nextEdge.verts:
self.verts.append[edge.verts[0]]
self.verts.append[edge.verts[1]]
else:
raise "Trying to define a polyline with a broken chain of edges!"
if self.verts[0] == self.verts[-1]: #If we have the same vert at the beginning and end, we have a closed polyline
self.closed = True
self.verts.pop()
def draw(self, context):
context.move_to(self.verts[0].loc2d.x, self.verts[0].loc2d.y)
for vert in self.verts[1:]:
context.line_to(vert.loc2d.x, vert.loc2d.y)
def drawSWF(self, shape, movie, width, color):
myShape = shape
myShape.setLine(width, color.r * 255,color.g * 255,color.b * 255,color.a * 255)
myShape.movePenTo(self.verts[0].loc2d.x, self.verts[0].loc2d.y)
for vert in self.verts[1:]:
myShape.drawLineTo(vert.loc.x, vert.loc.y)
return [myShape]
def append(self, edgeList = [], vertList = []):
if edgeList:
if self.verts[-1] not in edgeList[0].verts:
raise("Trying to append unconnected group of edges to polyline!")
else:
for edge in edgeList:
if self.verts[-1] == edge.verts[0]:
self.verts.append(edge.verts[1])
elif self.verts[-1] == edge.verts[1]:
self.verts.append(edge.verts[0])
else:
raise("Trying to define a polyline with a broken chain of edges!")
elif vertList:
for vert in vertList:
self.verts.append(vert)
def extend(self, otherPolyline):
if otherPolyline.verts[0] == self.verts[-1]: #end to start connection
self.verts.extend(otherPolyline.verts)
self.edges.extend(otherPolyline.edges)
elif otherPolyline.verts[0] == self.verts[0]: #start to start connection
self.verts.reverse()
self.edges.reverse()
self.verts.extend(otherPolyline.verts)
self.edges.extend(otherPolyline.edges)
elif otherPolyline.verts[-1] == self.verts[-1]: #end to end connection
otherPolyline.verts.reverse()
otherPolyline.edges.reverse()
self.verts.extend(otherPolyline.verts)
self.edges.extend(otherPolyline.edges)
else:
raise("Trying to connect two polylines which do not connect!")
#---PIPELINE FUNCTIONS (IN ORDER OF APPLICATION)---#000000#FFFFFF--------------------------------------
def blender2Pantograph(drawing, frame = None):
"""
bring in mesh data from Blender
"""
#a little Blender housekeeping
if Blender.Window.EditMode(): Blender.Window.EditMode(0)
#set up the scene and rendering context
scene = drawing.scene
blenderScene = Blender.Scene.GetCurrent()
blenderRender = blenderScene.getRenderingContext()
#set the proper frame, if necessary
try:
blenderRender.currentFrame(frame)
progress('Rendering frame %i...' % frame)
except:
progress('Rendering current frame...')
#set up the camera location and matrix
blenderCamera = blenderScene.objects.camera#getCurrentCamera()
progress('Setting up camera:')
progress(' near clip: %f' % blenderCamera.getData().clipStart)
progress(' far clip: %f' % blenderCamera.getData().clipEnd)
progress(' fov(degrees): %f' % blenderCamera.getData().angle)
progress(' scale: %f' % blenderCamera.getData().scale)
scene.cameraMatrix = blenderCamera.getInverseMatrix()
#set up the FRUSTUM
drawing.WIDTH=float(blenderRender.imageSizeX())
drawing.HEIGHT=float(blenderRender.imageSizeY())
scene.aspect = drawing.WIDTH / drawing.HEIGHT
#scene.fovy = blenderCamera.getData().angle/scene.aspect
scene.fovx = blenderCamera.getData().angle
scene.near = blenderCamera.getData().clipStart
scene.far = blenderCamera.getData().clipEnd
scene.scale = blenderCamera.getData().scale
if blenderCamera.getData().type == 'ortho':
scene.cam2ClipTransform = scene.getOrthoMatrix(scene.aspect,scene.near,scene.far,scene.scale)
scene.projection = 'ortho'
elif blenderCamera.getData().type == 'persp':
scene.cam2ClipTransform = scene.getPerspMatrix(scene.fovx,scene.aspect,scene.near,scene.far)
scene.projection = 'persp'
debug('World-space camera location:',blenderCamera.loc)
debug('Camera-space camera location:',Vector(blenderCamera.loc).resize4D() * scene.cameraMatrix)
scene.camera = Vector(blenderCamera.loc).resize4D() * scene.cameraMatrix
#get the current world background color(s)
blenderWorld = Blender.World.GetCurrent()
drawing.zenithColor = RGBA(blenderWorld.getZen()[0], blenderWorld.getZen()[1], blenderWorld.getZen()[2], 1)
drawing.horizonColor = RGBA(blenderWorld.getHor()[0], blenderWorld.getHor()[1], blenderWorld.getHor()[2], 1)
#get the scene's objects
blenderObjList=blenderScene.objects
proxyObj = Blender.Object.New('Mesh','temp')
for blenderObj in blenderObjList:
if len(intersect(blenderObj.layers, blenderScene.layers)) == 0:
progress("Object %s is not visible..." % blenderObj.name)
elif blenderObj.type == 'Mesh' or \
blenderObj.type == 'Surf' or \
blenderObj.type == 'MBall' or \
blenderObj.type == 'Curve' or \
blenderObj.type == 'Mesh' or \
blenderObj.type == 'Text':
progress("Processing object %s" % blenderObj.name)
blenderMesh = Blender.Mesh.New()
blenderMesh.getFromObject(blenderObj, 0, 1)
Blender.Mesh.Mode(Blender.Mesh.SelectModes['FACE'])
proxyObj.link(blenderMesh)
blenderScene.objects.link(proxyObj)
vertDict={}
blenderMeshEdges = []
mesh = Mesh(scene, blenderObj.name)
#get the layers from the Blender object
mesh.layers = set(blenderObj.layers)
#set the mesh crease angle to the blender mesh's smoothing angle
mesh.creaseAngle = blenderMesh.degr
blenderMaterials = blenderMesh.materials
progress("Triangulating faces...")
blenderMeshEdges = [edge for edge in blenderMesh.edges]#this will give us a list of the edges BEFORE triangulation
for blenderFace in blenderMesh.faces:
blenderFace.sel = 1
try:
blenderMesh.quadToTriangle()
except:
debug("No triangulation is required...")
blenderMesh.update()
proxyObj.makeDisplayList()
Blender.Window.RedrawAll()
#transform the mesh to get the world coordinates
## objMatrix = blenderObj.getMatrix()
## myTransform = objMatrix * scene.cameraMatrix * scene.cam2ClipTransform
## blenderMesh.transform(myTransform)
#VERTS####################
progress("Processing %i vertices..." % len(blenderMesh.verts))
#Get the World matrix of the Object
objMatrix = blenderObj.getMatrix().copy().resize4x4()
#Object World Matrix x Camera Inverse Matrix
myTransform = objMatrix * scene.cameraMatrix * scene.cam2ClipTransform
def normalize(vector):
return (vector.x/vector.w, vector.y/vector.w, vector.z/vector.w)
vertLocList = [Vector(vert.co).resize4D() * myTransform for vert in blenderMesh.verts]
vertList = [Vert(scene, loc = loc) for loc in vertLocList]
## vertLocList = (Vector(vert.co).resize4D() for vert in blenderMesh.verts)
## vertList = (Vert(scene, loc = loc) for loc in vertLocList)
setItem = vertDict.__setitem__
[setItem(blenderVert, vert) for (blenderVert, vert) in zip(blenderMesh.verts, vertList)]
#EDGES####################
progress("Processing %i edges..." % len(blenderMesh.edges))
edgeList = [Edge(vertList, mesh) for vertList in [(vertDict[edge.v1], vertDict[edge.v2]) for edge in blenderMesh.edges]]
for (blenderEdge, edge) in zip(blenderMesh.edges, edgeList):
if blenderEdge.flag & Blender.Mesh.EdgeFlags.SHARP:
edge.SHARP = True
elif blenderEdge in blenderMeshEdges:
edge.MESH = True
#FACES/CURVES####################
if len(blenderMesh.faces) > 0:
progress ("Processing %i faces..."% len(blenderMesh.faces))
faceVertLists = [[vertDict[face.verts[0]],vertDict[face.verts[1]],vertDict[face.verts[2]]] for face in blenderMesh.faces]
faceList = [Face(mesh,vertList) for vertList in faceVertLists]
[computeNormal(face) for face in faceList]
classifyList = (classifyPointPlane(scene.camera, face.plane) for face in faceList)
for (classify, face) in zip(classifyList, faceList):
if classify == IN_FRONT:
face.facing = FRONT_FACING
else:
face.facing = BACK_FACING
#set bounding box for each face
bboxList = (BoundingBox(face.verts) for face in faceList)
for (bbox,face) in zip(bboxList, faceList):
face.bbox = bbox
else:
#this must mean we have a curve (or a mesh with edges but no faces)
progress ("Processing curve...")
mesh.curves = [Curve(mesh, vertDict.values())]
#MATERIALS####################
progress ("Processing materials...")
if mesh.faces:
for (blenderFace, face) in zip(blenderMesh.faces, faceList):
try:
myMaterial = blenderMaterials[blenderFace.mat]
except:
myMaterial = None #this covers objects with no material
if myMaterial and not myMaterial.name in drawing.settings.materialDefaults.keys():#convert the material to an RGBA
drawing.settings.materialDefaults[myMaterial.name] = RGBA( myMaterial.R,
myMaterial.G,
myMaterial.B,
myMaterial.alpha )
if myMaterial:
if myMaterial.name == "CUT" or myMaterial.name == "Cut" or myMaterial.name == "cut":#for cutting faces
face.type = CUTTING_TYPE
face.mesh.type = CUTTING_TYPE
face.materialName = 'DEFAULT'
elif myMaterial.name == "VIGNETTE" or myMaterial.name == "Vignette" or myMaterial.name == "vignette":#for vignette faces
face.type = VIGNETTE_TYPE
face.mesh.type = VIGNETTE_TYPE
face.materialName = 'DEFAULT'
elif myMaterial:
face.materialName = myMaterial.name
else: #this covers objects with no material
face.materialName = 'DEFAULT'
elif mesh.curves:
blenderCurve = blenderObj.data
for curve in mesh.curves:
try:
myMaterial = blenderCurve.getMaterials()[0]
except:
myMaterial = None #this covers objects with no material
if myMaterial and not myMaterial.name in drawing.settings.materialDefaults.keys():#convert the material to an RGBA
drawing.settings.materialDefaults[myMaterial.name] = RGBA( myMaterial.R,
myMaterial.G,
myMaterial.B,
myMaterial.alpha )
if myMaterial:
if myMaterial.name == "CUT" or myMaterial.name == "Cut" or myMaterial.name == "cut":
#curves cannot be cutting faces, so this is set to DEFAULT
curve.materialName = 'DEFAULT'
elif myMaterial.name == "VIGNETTE" or myMaterial.name == "Vignette" or myMaterial.name == "vignette":
#curves cannot be vignette faces, so this is set to DEFAULT
curve.materialName = 'DEFAULT'
elif myMaterial:
curve.materialName = myMaterial.name
else: #this covers objects with no material
curve.materialName = 'DEFAULT'
blenderScene.objects.unlink(proxyObj)
progress ("Writing materials to Pen Settings...")
for materialName in drawing.settings.materialDefaults.keys():
if not materialName in drawing.settings.pens.keys() and not (materialName == "CUT" or materialName == "cut" or materialName == "Cut") and not (materialName == "VIGNETTE" or materialName == "vignette" or materialName == "Vignette"):
drawing.settings.pens[materialName] = deepcopy(drawing.settings.pens['DEFAULT']) #for now...
drawing.settings.pens[materialName].attributeDict["FILLCOLOR"] = drawing.settings.materialDefaults[materialName]
drawing.settings.pens[materialName].name = materialName+"_pen"
drawing.settings.penStorage.append(drawing.settings.pens[materialName])
def buildConnectivity(drawing):
"""
builds topology for the mesh objects
"""
scene = drawing.scene
settings = drawing.settings
progress("Building connectivity for meshes...")
if (settings.CULL_BACKFACES): progress("Culling back-facing triangles...")
if (settings.AUTO_CREASE): progress ('Automatically detecting crease edges...')
for mesh in scene.meshes:
if ((mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE)):
for face in mesh.faces:
for (vert, nextVert) in zip(face.verts, face.verts[1:]+[face.verts[0]]):
hasEdge = False
for edge in vert.edges:
if nextVert in edge.verts:
if not edge in face.edges:
face.edges.append(edge)
if not face in edge.faces:
edge.faces.append(face)
hasEdge = True
if not hasEdge:
raise("Error in mesh %s:Should have produced a corresponding edge! Please report this bug to Pantograph development team" % mesh.name)
if not len(face.edges)==3:
raise ("Wrong number of edges in mesh %s" % mesh.name)
for curve in mesh.curves:
for vert in curve.verts:
hasEdge = False
for edge in vert.edges:
if vert in edge.verts:
if not edge in curve.edges: curve.edges.append(edge)
hasEdge = True
if not hasEdge:
raise("Error in mesh %s:Should have produced a corresponding edge! Please report this bug to Pantograph development team" % mesh.name)
#Handle back-face culling, if needed:
if mesh.faces:
if (settings.CULL_BACKFACES) and (mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE):
faceDestroyList = [face for face in mesh.faces if face.facing == BACK_FACING]
[face.destroy() for face in faceDestroyList]
for face in faceDestroyList: del(face)
#remove degenerate edges for non-curve meshes, or any edges left over from backface cull
### We should provide a possibility to have non-attached edges become curves!
edgeDestroyList = []
for edge in mesh.edges:
if len(edge.faces) == 0: #orphaned edge: just discard it
edgeDestroyList.append(edge)
elif len(edge.faces)<2 or (edge.faces[0].facing != edge.faces[1].facing):
edge.SILHOUETTE = True
[edge.destroy() for edge in edgeDestroyList]
for edge in edgeDestroyList: del(edge)
#automatically find creases, if necessary
if len(mesh.faces) > 0 and (settings.AUTO_CREASE) and (mesh.type != CUTTING_TYPE) and (mesh.type != VIGNETTE_TYPE):
[doCrease(edge, mesh.creaseAngle) for edge in mesh.edges]
def makeFrustum(scene):
"""
Creates a frustum cube for a frustum cull
"""
progress("Creating the camera frustum...")
#generate the necessary frustum points:
if scene.projection == "persp":
cameraLoc = scene.camera.resize3D()
cameraViewVector = -Vector(0,0,1)
cameraUpVector = -Vector(0,1,0)
cameraRightVector = -Vector(1,0,0)
nearCenter = cameraLoc + cameraViewVector * scene.near
nearWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.near
nearHeight = nearWidth / scene.aspect
farCenter = (cameraLoc + cameraViewVector) * scene.far
farWidth = 2 * tan(radians(scene.fovx * 0.5)) * scene.far
farHeight = farWidth / scene.aspect
else:
cameraLoc = scene.camera.resize3D()
cameraViewVector = -Vector(0,0,1)
cameraUpVector = Vector(0,1,0)
cameraRightVector = Vector(1,0,0)
nearCenter = cameraLoc + cameraViewVector * scene.near
nearWidth = float(scene.scale)
nearHeight = float(scene.scale/scene.aspect)
farCenter = (cameraLoc + cameraViewVector) * scene.far
farWidth = nearWidth
farHeight = nearHeight
nearLeftTop = Vert(scene, ((nearCenter + (cameraUpVector * nearHeight/2) - (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
nearRightTop = Vert(scene, ((nearCenter + (cameraUpVector * nearHeight/2) + (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
nearRightBottom = Vert(scene, ((nearCenter - (cameraUpVector * nearHeight/2) + (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
nearLeftBottom = Vert(scene, ((nearCenter - (cameraUpVector * nearHeight/2) - (cameraRightVector * nearWidth/2)).resize4D()) * scene.cam2ClipTransform)
farLeftTop = Vert(scene, ((farCenter + (cameraUpVector * farHeight/2) - (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
farRightTop = Vert(scene, ((farCenter + (cameraUpVector * farHeight/2) + (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
farRightBottom = Vert(scene, ((farCenter - (cameraUpVector * farHeight/2) + (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
farLeftBottom = Vert(scene, ((farCenter - (cameraUpVector * farHeight/2) - (cameraRightVector * farWidth/2)).resize4D()) * scene.cam2ClipTransform)
debug( 'Camera Location:', cameraLoc)
debug(' Camera frustum:')
debug(' near_WxH:', nearHeight, nearWidth)
debug(' far_WxH:', farHeight, farWidth)
debug(' near_center:', nearCenter)
debug(' near_top_left: ',nearLeftTop.loc)
debug(' near_top_right: ',nearRightTop.loc)
debug(' near_bottom_left: ',nearLeftBottom.loc)
debug('near_bottom_right: ',nearRightBottom.loc)
debug(' far_top_left: ',farLeftTop.loc)
debug(' far_top_right: ',farRightTop.loc)
debug(' far_bottom_left: ',farLeftBottom.loc)
debug(' far_bottom_right: ',farRightBottom.loc)
frustumMesh = Mesh(scene, "Frustum")
frustumMesh.type = FRUSTUM_TYPE
nearFace = Face(frustumMesh, [nearRightTop, nearLeftTop, nearLeftBottom, nearRightBottom])
nearFace.type = FRUSTUM_TYPE
farFace = Face(frustumMesh, [farLeftTop, farRightTop, farRightBottom, farLeftBottom])
farFace.type = FRUSTUM_TYPE
topFace = Face(frustumMesh, [nearLeftTop, nearRightTop, farRightTop, farLeftTop])
topFace.type = FRUSTUM_TYPE
bottomFace = Face(frustumMesh, [nearRightBottom, nearLeftBottom, farLeftBottom, farRightBottom])
bottomFace.type = FRUSTUM_TYPE
rightFace = Face(frustumMesh, [farRightTop, nearRightTop, nearRightBottom, farRightBottom])
rightFace.type = FRUSTUM_TYPE
leftFace = Face(frustumMesh, [nearLeftTop, farLeftTop, farLeftBottom, nearLeftBottom])
leftFace.type = FRUSTUM_TYPE
nearFace.facing = FRONT_FACING
farFace.facing = BACK_FACING
topFace.facing = BACK_FACING
bottomFace.facing = BACK_FACING
rightFace.facing = BACK_FACING
leftFace.facing = BACK_FACING
[computeNormal(face) for face in frustumMesh.faces]
if scene.projection == "ortho":[face.normal.negate() for face in frustumMesh.faces] #the ortho projection matrix is mirrored, producing a reversed cube
#Remove faces outside the frustum
progress("Frustum culling...")
#remove cutting faces from scene.faces
for face in frustumMesh.faces:
scene.faces.remove(face)
#create bounding boxes for the cutting meshes
myVerts = [nearLeftTop, nearRightTop, nearRightBottom, nearLeftBottom, farLeftTop, farRightTop, farRightBottom, farLeftBottom]
frustumMeshBbox = BoundingBox(myVerts)
#make a list of the faces that fall within the bounding box of the cutting mesh
cutawayList = [face for face in scene.faces if (face.mesh.type == GEOMETRY_TYPE and frustumMeshBbox.doesIntersect(face.bbox))]
#split all the faces remaining in cutawayList
for cuttingFace in frustumMesh.faces:
myCutawayList = [] #faces that might be removed by the cutting mesh
classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
#classify all the faces according to the splitting face (setting classifyDict in the process)
[classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
#make a list of either classifications or splits
splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
for (classify, cutFace) in zip(splitList, cutawayList):
#we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
if classify == IN_FRONT:
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(cutFace)
elif classify == BEHIND:
if cuttingFace.facing == FRONT_FACING:
myCutawayList.append(cutFace)
elif classify == ON:
myCutawayList.append(cutFace)
else:#we have an intersection
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(classify[0])
elif cuttingFace.facing == FRONT_FACING:
myCutawayList.append(classify[1])
cutawayList = copy(myCutawayList)
keepSet = set(cutawayList)
#we want to destroy all scene faces EXCEPT for the ones we have selected:
destroySet = scene.faces.difference(keepSet)
#destroy all cutaway faces
progress("Number of faces removed outside of frustum: %i" % len(destroySet))
for cutFace in destroySet:
if cutFace.mesh.type == GEOMETRY_TYPE:
for edge in cutFace.edges:
edge.SILHOUETTE = False
edge.CUT = False
cutFace.destroy()
del(cutFace)
def doVignettes(scene):
progress("Removing faces outside vignette...")
cuttingMeshList = []
for mesh in scene.meshes:
if mesh.type == VIGNETTE_TYPE:
cuttingMeshList.append(mesh)
if cuttingMeshList:
#remove cutting faces from scene.faces
for mesh in cuttingMeshList:
for face in mesh.faces:
scene.faces.remove(face)
#create bounding boxes for the cutting meshes
cuttingMeshBboxList = []
cuttingFaceVertList = []
for mesh in cuttingMeshList:
extend = cuttingFaceVertList.extend
[extend(face.verts) for face in mesh.faces]
cuttingMeshBboxList.append(BoundingBox(cuttingFaceVertList))
#create a set to contain faces to be kept
keepSet = set()
#compare all other faces against cutting faces
for (mesh, cuttingBbox) in zip(cuttingMeshList, cuttingMeshBboxList):
#make a list of the faces that fall within the bounding box of the cutting mesh
cutawayList = [face for face in scene.faces if (cuttingBbox.doesIntersect(face.bbox) and face.mesh.type == GEOMETRY_TYPE)]
#split all the faces remaining in cutawayList
for cuttingFace in mesh.faces:
myCutawayList = [] #faces that might be removed by the cutting mesh
classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
#classify all the faces according to the splitting face (setting classifyDict in the process)
[classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
#make a list of either classifications or splits
splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
for (classify, cutFace) in zip(splitList, cutawayList):
#we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
if classify == IN_FRONT:
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(cutFace)
elif classify == BEHIND:
if cuttingFace.facing == FRONT_FACING:
myCutawayList.append(cutFace)
elif classify == ON:
myCutawayList.append(cutFace)
else:#we have an intersection
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(classify[0])
elif cuttingFace.facing == FRONT_FACING:
myCutawayList.append(classify[1])
cutawayList = copy(myCutawayList)
keepSet = keepSet.union(set(cutawayList))
#we want to destroy all scene faces EXCEPT for the ones we have selected:
destroySet = scene.faces.difference(keepSet)
#destroy all cutaway faces
progress("Number of faces removed outside of vignettes: %i" % len(destroySet))
for cutFace in destroySet:
if cutFace.mesh.type == GEOMETRY_TYPE:
for edge in cutFace.edges:
if not edge.CUTISVISIBLE:
edge.CUT = True
else:
edge.SILHOUETTE = True
cutFace.destroy()
del(cutFace)
def doCutaways(scene):
progress("Removing cutaway faces...")
#make a dict of the cutting faces by mesh
cuttingMeshList = []
for mesh in scene.meshes:
if mesh.type == CUTTING_TYPE:
cuttingMeshList.append(mesh)
#remove cutting faces from scene.faces
for mesh in cuttingMeshList:
for face in mesh.faces:
scene.faces.remove(face)
#create bounding boxes for the cutting meshes
cuttingMeshBboxList = []
cuttingFaceVertList = []
for mesh in cuttingMeshList:
extend = cuttingFaceVertList.extend
[extend(face.verts) for face in mesh.faces]
cuttingMeshBboxList.append(BoundingBox(cuttingFaceVertList))
#compare all other faces against cutting faces
for (mesh, cuttingBbox) in zip(cuttingMeshList, cuttingMeshBboxList):
#make a list of the faces that fall within the bounding box of the cutting mesh
cutawayList = [face for face in scene.faces if (face.mesh.layers.intersection(mesh.layers) and cuttingBbox.doesIntersect(face.bbox))]
#split all the faces remaining in cutawayList
for cuttingFace in mesh.faces:
myCutawayList = [] #faces that might be removed by the cutting mesh
classifyDict = dict() #a dict with the classifications of all the verts according to the cutting face
#classify all the faces according to the splitting face (setting classifyDict in the process)
[classifyFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
#make a list of either classifications or splits
splitList = [splitFaceFace(cutFace, cuttingFace, classifyDict) for cutFace in cutawayList]
for (classify, cutFace) in zip(splitList, cutawayList):
#we need to determine the absolute classification, so we un-correct for the facing of the cutting face!
if classify == IN_FRONT:
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(cutFace)
elif classify == BEHIND:
if cuttingFace.facing == FRONT_FACING:
myCutawayList.append(cutFace)
elif classify == ON:
myCutawayList.append(cutFace)
else:#we have an intersection
if cuttingFace.facing == BACK_FACING:
myCutawayList.append(classify[0])
elif cuttingFace.facing == FRONT_FACING:
myCutawayList.append(classify[1])
cutawayList = copy(myCutawayList)
#destroy all cutaway faces
progress("Number of faces removed in cut-away: %i" % len(cutawayList))
for cutFace in cutawayList:
for edge in cutFace.edges:
if edge.CUTISVISIBLE:
edge.CUT = True
else:
edge.SILHOUETTE = True
cutFace.destroy()
del(cutFace)
def occludeAndCull(drawing):
scene = drawing.scene
zTopList=[]
zCurrentList=[]
frontOccludingRegion = Region(scene)
visiblePolygonList = []
hiddenPolygonList = []
face2PolygonDict = dict()
progress("Sorting faces by depth and culling occluded faces...")
#generate window coords for all verts
[clip2Window(vert, drawing) for vert in scene.verts]
#sort faces by the z-value closest to the camera (zMin)
[insort(zTopList,(face.bbox.zMin,face)) for face in scene.faces]
#Look at each face in zTopList:
#make occluding regions (in 2D coords for each face)
for pair in zTopList:
face = pair[1]
face2PolygonDict[face] = Region(scene,vertList = face.verts)
#check if any of the current faces can be popped off, and add them to the sorted list
removalList = []
for currentFace in zCurrentList:
if currentFace.bbox.zMax < face.bbox.zMin: #currentFace is entirely in front of the face
if drawing.settings.pens[currentFace.materialName].attributeDict["FILLCOLOR"].a == 1: #transparent faces do not occlude
frontOccludingRegion = frontOccludingRegion + face2PolygonDict[currentFace]
visiblePolygonList.append(currentFace)
removalList.append(currentFace)
for currentFace in removalList:
zCurrentList.remove(currentFace)
#check if our face is occluded by the current region
if frontOccludingRegion.covers(face2PolygonDict[face]):
hiddenPolygonList.append(face)
#if not occluded, put the polygon in the current list
else:
zCurrentList.append(face)
debug('mark 3')
for face in zCurrentList:
visiblePolygonList.append(face)#put anything left in zCurrentList on the end of visiblePolygonList
progress("Number of faces removed before BSP sort: %i" % len(hiddenPolygonList))
progress("Number of faces going into BSP tree: %i" % len(visiblePolygonList))
return (visiblePolygonList, hiddenPolygonList)
def BSPSort(scene, visibleFaces):
"""
Sorts the scene's polygons into a BSP Tree, using
the Least-Crossed method to chose new node
"""
Sorting = Profiler('Sorting')
Sorting.start()
faceList = visibleFaces
curveList = []
for mesh in scene.meshes:
if mesh.curves:
curveList.append(mesh.curves[0])
drawingQueue = []
#sort the faces in a BSP tree
TOTAL_FACES = len(faceList)
progress('Sorting meshes into BSP tree...')
myBSPTree = BSPTree(faceList, curveList)
drawingQueue = myBSPTree.backToFront(0)
progress(Sorting.end())
global CLASSIFY_CALLS
global SPLIT_CALLS
global MAX_DEPTH
print("Maximum tree depth is %i" % MAX_DEPTH)
print("CLASSIFY calls: %i" % CLASSIFY_CALLS)
print("SPLIT calls: %i" % SPLIT_CALLS)
print( "Faces after splitting: %i" % len(drawingQueue) )
return drawingQueue
def clipAndCombine(drawing):
"""
Sorts faces and edges into dicts by materialName. Transparent faces are sorted into a queue to allow preservation of layers.
Returns a tuple of (dict of fills, dict of polylines, queue of transparent fills)
"""
Clipping = Profiler('2D clipping')
Clipping.start()
scene = drawing.scene
#make sure all verts have a 2d location
[clip2Window(vert, drawing) for vert in drawing.scene.verts]
drawing.drawingQueue.reverse()#for painter's algorithm
#these regions are used for masking and clipping
drawing.lineClippingRegion = Region(scene)
if drawing.settings.USE_ALPHA:drawing.fillClippingRegion = Region(scene)
drawing.fillDict = dict()
drawing.transparentFillDict = dict()
drawing.transparentFillQueue = list()
drawing.currentTransparentRegion = list()
drawing.material2PolylinesDict = dict()
#initialize the dicts for storing polylines and fills
progress('Initializing polyline and fill dicts...')
for object in (drawing.drawingQueue + drawing.hiddenPolygons):
if not object.materialName in drawing.fillDict.keys():
drawing.fillDict[object.materialName] = dict()
drawing.transparentFillDict[object.materialName] = dict()
if not object.mesh in drawing.fillDict[object.materialName].keys():
drawing.fillDict[object.materialName][object.mesh] = Region(scene)
drawing.transparentFillDict[object.materialName][object.mesh] = Region(scene)
if not object.materialName in drawing.material2PolylinesDict.keys():
drawing.material2PolylinesDict[object.materialName] = dict()
if not object.mesh in drawing.material2PolylinesDict[object.materialName].keys():
drawing.material2PolylinesDict[object.materialName][object.mesh] = dict()
myPolylineDict = drawing.material2PolylinesDict[object.materialName][object.mesh]
#initialize the material's polylinedict, if necessary
if not 'SILHOUETTE' in myPolylineDict.keys():
myPolylineDict['SILHOUETTE'] = []
if not 'CREASE' in myPolylineDict.keys():
myPolylineDict['CREASE'] = []
if not 'CUT' in myPolylineDict.keys():
myPolylineDict['CUT'] = []
if not 'MESH' in myPolylineDict.keys():
myPolylineDict['MESH'] = []
if not 'HIDDEN' in myPolylineDict.keys():
myPolylineDict['HIDDEN'] = []
if not 'CURVE' in myPolylineDict.keys():
myPolylineDict['CURVE'] = []
#make a Region from each face, grouping by material and then by mesh
progress('Classifying faces by material and by mesh...')
[clipObjects(drawing, object, drawing.material2PolylinesDict[object.materialName][object.mesh], True) for object in drawing.hiddenPolygons]
[clipObjects(drawing, object, drawing.material2PolylinesDict[object.materialName][object.mesh], False) for object in drawing.drawingQueue]
#combine line segments
progress('Combining segments into polylines...')
[combinePolylines(drawing, polylineDict) for material in drawing.material2PolylinesDict.keys() \
for mesh in drawing.material2PolylinesDict[material].keys() \
for polylineDict in drawing.material2PolylinesDict[material].values() ]
if (not drawing.currentTransparentRegion in drawing.transparentFillQueue) and drawing.currentTransparentRegion: drawing.transparentFillQueue.append(drawing.currentTransparentRegion)
drawing.transparentFillQueue.reverse()
drawing.globalSilhouetteRegion = drawing.lineClippingRegion
progress(Clipping.end())
#---CAIRO-RELATED CLASSES---#000000#FFFFFF--------------------------------------
class RGBA:
def __init__(self,r=1.0,g=1.0,b=1.0,a=1.0):
self.r=r
self.g=g
self.b=b
self.a=a
def __getitem__(self):
return
class Pen(RGBA):
BLACK=RGBA(0,0,0,1)
WHITE=RGBA(1,1,1,1)
GREYTRANSPARENT=RGBA(0.5,0.5,0.5,0.5)
GREYOPAQUE=RGBA(0.5,0.5,0.5,1)
DASH=(5,5)
DOTDASH=(4,4,10,4)
DOT=(1,4)
SOLID=(1,0)
MITER = cairo.LINE_JOIN_MITER
FILLET = cairo.LINE_JOIN_ROUND
BEVEL = cairo.LINE_JOIN_BEVEL
BUTT = cairo.LINE_CAP_BUTT
ROUND = cairo.LINE_CAP_ROUND
SQUARE = cairo.LINE_CAP_SQUARE
lineTypeList = ["SILHOUETTE", "CREASE", "MESH", "HIDDEN", "CUT", "CURVE", "MATERIAL SILHOUETTE"]
def __init__(self, attributeDict =
{"SILHOUETTE": {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.75, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"CREASE": {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"MESH": {"Visible":True, "lineColor":RGBA(0.5,0.5,0.5,1), "lineWidth":0.25, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"HIDDEN": {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(5,5), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"CUT": {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":2.0, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"CURVE": {"Visible":True, "lineColor":RGBA(0,0,0,1), "lineWidth":0.5, "lineDash":(4,4,10,4), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"MATERIAL SILHOUETTE": {"Visible":False, "lineColor":BLACK, "lineWidth":0.25, "lineDash":(1,0), "lineJoin" : cairo.LINE_JOIN_BEVEL, "lineCap":cairo.LINE_CAP_ROUND},
"FILLCOLOR": RGBA(0.5,0.5,0.5,1),
"FILLRASTER": {"toggle":False, "filename":""}},
name = 'DEFAULT_pen'):
self.attributeDict = attributeDict
self.name = name
class Drawing:
def __init__(self):
self.scene = Scene()
self.rendered = False
self.drawingQueue = []
self.hiddenPolygons = []
self.visiblePolygons = []
self.fillDict = False
self.hiddenPolylinesDict = False
self.transparentFillDict = False
self.globalSilhouetteRegion = Region(self.scene)
self.rasterFiles = dict()
self.settings = Settings()
self.dashTypes = {'DASH':Pen.DASH, 'DOTDASH':Pen.DOTDASH, 'DOT':Pen.DOT, 'SOLID':Pen.SOLID}
self.dashTypesList = self.dashTypes.keys()
self.dashTypesList.sort()
self.endStyles = {'BUTT':Pen.BUTT, 'ROUND':Pen.ROUND, 'SQUARE':Pen.SQUARE}
self.endStylesList = self.endStyles.keys()
self.endStylesList.sort()
self.joinStyles = {'MITER':Pen.MITER, 'FILLET':Pen.FILLET, 'BEVEL':Pen.BEVEL}
self.joinStylesList = self.joinStyles.keys()
self.joinStylesList.sort()
self.progressMaker = None #This gets set by the GUI
def render(self, width = 0, height = 0, filename = 'temp.svg', canvas = 'screen'):
"""
This method handles the actual rendering of the Drawing
"""
if canvas == 'screen':
drawable = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width), int(height))
context = cairo.Context(drawable)
context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
elif canvas == 'svg':
file = open(filename, mode = 'w')
drawable = cairo.SVGSurface(filename, width/1.25, height/1.25)
context = cairo.Context(drawable)
context.scale(0.8,0.8) #this corrects the mysterious Cairo 125% scaling!
elif canvas == 'pdf':
file = open(filename, mode = 'w')
drawable = cairo.PDFSurface(filename, width, height)
context = cairo.Context(drawable)
elif canvas == 'png':
drawable = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width), int(height))
context = cairo.Context(drawable)
context.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
#Make a screen-sized rectangle and use it to clip the whole drawing
clipRectangle = context.new_path()
context.move_to(0,0)
context.line_to(self.WIDTH,0)
context.line_to(self.WIDTH,self.HEIGHT)
context.line_to(0,self.HEIGHT)
context.close_path()
context.clip()
#Make another screen-sized rectangle and fill it with a gradient matching the Blender horizon and zenith colors for the background
background = cairo.SurfacePattern(context.get_target())
gradient = cairo.LinearGradient(0,0,0,self.HEIGHT)
cairo.LinearGradient.add_color_stop_rgb(gradient, 0, self.zenithColor.r, self.zenithColor.g, self.zenithColor.b)
cairo.LinearGradient.add_color_stop_rgb(gradient, 1, self.horizonColor.r, self.horizonColor.g, self.horizonColor.b)
context.set_source(gradient)
context.new_path()
context.move_to(0,0)
context.line_to(0, self.HEIGHT)
context.line_to(self.WIDTH, self.HEIGHT)
context.line_to(self.WIDTH, 0)
context.close_path()
context.fill()
#first, draw the opaque fills:
for material in self.fillDict.keys():
for mesh in self.fillDict[material].keys():
myPen = self.settings.pens[material]
if myPen.attributeDict["FILLRASTER"]["toggle"] and myPen.attributeDict["FILLRASTER"]["filename"]:
#check to make sure the file is loaded
if not myPen.attributeDict["FILLRASTER"]["filename"] in self.rasterFiles.keys():
myRasterSurface = cairo.ImageSurface.create_from_png(myPen.attributeDict["FILLRASTER"]["filename"])
self.rasterFiles[myPen.attributeDict["FILLRASTER"]["filename"]] = myRasterSurface
myRasterSurface = self.rasterFiles[myPen.attributeDict["FILLRASTER"]["filename"]]
context.set_source_surface(myRasterSurface)
else:
context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
myPen.attributeDict["FILLCOLOR"].g,
myPen.attributeDict["FILLCOLOR"].b,
myPen.attributeDict["FILLCOLOR"].a)
context.set_line_width(0)
context.new_path()
self.fillDict[material][mesh].draw(context)
context.fill()
#second, draw the transparent fills in queue order:
for pair in self.transparentFillQueue:
myPen = self.settings.pens[pair[0]]
context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
myPen.attributeDict["FILLCOLOR"].g,
myPen.attributeDict["FILLCOLOR"].b,
myPen.attributeDict["FILLCOLOR"].a)
context.set_line_width(0)
context.new_path()
pair[1].draw(context)
context.fill()
#draw the material silhouettes:
for material in self.fillDict.keys():
for mesh in self.fillDict[material].keys():
myPen = self.settings.pens[material]
if myPen.attributeDict["MATERIAL SILHOUETTE"]["Visible"]:
context.set_line_join(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineCap"])
context.set_source_rgba(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].r,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].g,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].b,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].a)
context.set_line_width(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"])
context.set_dash(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineDash"])
context.new_path()
self.fillDict[material][mesh].outline(context)
context.stroke()
for material in self.transparentFillDict.keys():
for mesh in self.transparentFillDict[material].keys():
myPen = self.settings.pens[material]
context.set_line_join(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineCap"])
context.set_source_rgba(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].r,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].g,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].b,
myPen.attributeDict["MATERIAL SILHOUETTE"]["lineColor"].a)
context.set_line_width(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"])
context.set_dash(myPen.attributeDict["MATERIAL SILHOUETTE"]["lineDash"])
if len(self.transparentFillDict[material][mesh].polygon):
context.new_path()
self.transparentFillDict[material][mesh].outline(context)
context.stroke()
#draw the polylines
for material in self.material2PolylinesDict.keys():
for mesh in self.material2PolylinesDict[material].keys():
myPen = self.settings.pens[material]
myPolylinesDict = self.material2PolylinesDict[material][mesh]
#next, draw the mesh edges:
if myPen.attributeDict["MESH"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["MESH"]["lineColor"].r,
myPen.attributeDict["MESH"]["lineColor"].g,
myPen.attributeDict["MESH"]["lineColor"].b,
myPen.attributeDict["MESH"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["MESH"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["MESH"]["lineCap"])
context.set_line_width(myPen.attributeDict["MESH"]["lineWidth"])
context.set_dash(myPen.attributeDict["MESH"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['MESH']:
polyline.draw(context)
context.stroke()
#next, draw the sharp(crease) edges:
if myPen.attributeDict["CREASE"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["CREASE"]["lineColor"].r,
myPen.attributeDict["CREASE"]["lineColor"].g,
myPen.attributeDict["CREASE"]["lineColor"].b,
myPen.attributeDict["CREASE"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["CREASE"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["CREASE"]["lineCap"])
context.set_line_width(myPen.attributeDict["CREASE"]["lineWidth"])
context.set_dash(myPen.attributeDict["CREASE"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['CREASE']:
polyline.draw(context)
context.stroke()
#next, draw the silhouette edges:
if myPen.attributeDict["SILHOUETTE"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["SILHOUETTE"]["lineColor"].r,
myPen.attributeDict["SILHOUETTE"]["lineColor"].g,
myPen.attributeDict["SILHOUETTE"]["lineColor"].b,
myPen.attributeDict["SILHOUETTE"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["SILHOUETTE"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["SILHOUETTE"]["lineCap"])
context.set_line_width(myPen.attributeDict["SILHOUETTE"]["lineWidth"])
context.set_dash(myPen.attributeDict["SILHOUETTE"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['SILHOUETTE']:
polyline.draw(context)
context.stroke()
#next, draw the cut edges:
if myPen.attributeDict["CUT"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["CUT"]["lineColor"].r,
myPen.attributeDict["CUT"]["lineColor"].g,
myPen.attributeDict["CUT"]["lineColor"].b,
myPen.attributeDict["CUT"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["CUT"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["CUT"]["lineCap"])
context.set_line_width(myPen.attributeDict["CUT"]["lineWidth"])
context.set_dash(myPen.attributeDict["CUT"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['CUT']:
polyline.draw(context)
context.stroke()
#next, draw the hidden lines
if myPen.attributeDict["HIDDEN"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["HIDDEN"]["lineColor"].r,
myPen.attributeDict["HIDDEN"]["lineColor"].g,
myPen.attributeDict["HIDDEN"]["lineColor"].b,
myPen.attributeDict["HIDDEN"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["HIDDEN"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["HIDDEN"]["lineCap"])
context.set_line_width(myPen.attributeDict["HIDDEN"]["lineWidth"])
context.set_dash(myPen.attributeDict["HIDDEN"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['HIDDEN']:
polyline.draw(context)
context.stroke()
#next, draw the curve lines
if myPen.attributeDict["CURVE"]["Visible"]:
context.set_source_rgba(myPen.attributeDict["CURVE"]["lineColor"].r,
myPen.attributeDict["CURVE"]["lineColor"].g,
myPen.attributeDict["CURVE"]["lineColor"].b,
myPen.attributeDict["CURVE"]["lineColor"].a)
context.set_line_join(myPen.attributeDict["CURVE"]["lineJoin"])
context.set_line_cap(myPen.attributeDict["CURVE"]["lineCap"])
context.set_line_width(myPen.attributeDict["CURVE"]["lineWidth"])
context.set_dash(myPen.attributeDict["CURVE"]["lineDash"])
context.new_path()
for polyline in myPolylinesDict['CURVE']:
if polyline.verts:
polyline.draw(context)
context.stroke()
#draw the global silhouette
if self.settings.globalLineVisible and self.scene.faces:
context.set_line_join(self.settings.globalLineJoin)
context.set_line_cap(self.settings.globalLineCap)
context.set_source_rgba(self.settings.globalLineColor.r,
self.settings.globalLineColor.g,
self.settings.globalLineColor.b,
self.settings.globalLineColor.a)
context.set_line_width(self.settings.globalLineWidth)
context.set_dash(self.settings.globalLineDash)
context.new_path()
self.globalSilhouetteRegion.outline(context)
context.stroke()
if canvas == 'svg' or canvas == 'pdf':
file.close()
elif canvas == 'png':
file = open(filename, mode = 'w')
drawable.write_to_png(filename)
file.close
else:
return drawable
def renderMovie(self, movie):
"""
This method handles rendering a number of frames to an swf movie
"""
#initialize the shape queue, if necessary
shapeQueue = list()
if self.shapes:
shape = (shape for shape in self.shapes)
else:
shape = repeat(ming.SWFShape())
#draw the screen background:
gradient = ming.SWFGradient()
gradient.addEntry(0.0, self.horizonColor.r * 255, self.horizonColor.g * 255, self.horizonColor.b * 255)
gradient.addEntry(self.HEIGHT/self.WIDTH, self.zenithColor.r * 255, self.zenithColor.g * 255, self.zenithColor.b * 255)
background = ming.SWFShape()
background.setLine(0,0,0,0,0)
gradientFill = background.addFill(gradient, ming.SWFFILL_LINEAR_GRADIENT)
gradientFill.rotateTo(90)
background.setRightFill(gradientFill)
background.drawLineTo(0,0)
background.drawLineTo(0,self.HEIGHT)
background.drawLineTo(self.WIDTH,self.HEIGHT)
background.drawLineTo(self.WIDTH,0)
movie.add(background)
#first, draw the opaque fills:
for material in self.fillDict.keys():
myShape = shape.next()
myPen = self.settings.pens[material]
for mesh in self.fillDict[material].keys():
self.fillDict[material][mesh].drawSWF(myShape, movie,myPen.attributeDict["FILLCOLOR"])
movie.add(myShape)
#second, draw the transparent fills in queue order:
for pair in self.transparentFillQueue:
myShape = shape.next()
movie.remove(myShape)
myPen = self.settings.pens[pair[0]]
pair[1].drawSWF(myShape, movie, myPen.attributeDict["FILLCOLOR"])
movie.add(myShape)
#draw the material silhouettes:
for material in self.fillDict.keys():
myPen = self.settings.pens[material]
if myPen.materialLineVisible:
myShape = shape.next()
movie.remove(myShape)
for mesh in self.fillDict[material].keys():
self.fillDict[material][mesh].outlineSWF(myShape, movie, myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"], myPen.attributeDict["fillColor"])
movie.add(myShape)
for material in self.transparentFillDict.keys():
myPen = self.settings.pens[material]
if myPen.materialLineVisible:
myShape = shape.next()
movie.remove(myShape)
for mesh in self.transparentFillDict[material].keys():
self.transparentFillDict[material][mesh].outlineSWF(myShape, movie, myPen.attributeDict["MATERIAL SILHOUETTE"]["lineWidth"], myPen.attributeDict["fillColor"])
movie.add(myShape)
#draw the polylines
for material in self.material2PolylinesDict.keys():
myPen = self.settings.pens[material]
for mesh in self.material2PolylinesDict[material].keys():
myPolylinesDict = self.material2PolylinesDict[material][mesh]
#next, draw the mesh edges:
if myPen.meshVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['MESH']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["MESH"]["lineWidth"], myPen.attributeDict["MESH"]["lineColor"])
movie.add(myShape)
#next, draw the sharp(crease) edges:
if myPen.creaseVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['CREASE']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["CREASE"]["lineWidth"], myPen.attributeDict["CREASE"]["lineColor"])
movie.add(myShape)
#next, draw the silhouette edges:
if myPen.silhouetteVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['SILHOUETTE']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["SILHOUETTE"]["lineWidth"], myPen.attributeDict["SILHOUETTE"]["lineColor"])
movie.add(myShape)
#next, draw the cut edges:
if myPen.cutVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['CUT']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["CUT"]["lineWidth"], myPen.attributeDict["CUT"]["lineColor"])
movie.add(myShape)
#next, draw the hidden lines
if myPen.hiddenVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['HIDDEN']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["HIDDEN"]["lineWidth"], myPen.attributeDict["HIDDEN"]["lineColor"])
movie.add(myShape)
#next, draw the curve lines
if myPen.curveVisible:
myShape = shape.next()
movie.remove(myShape)
for polyline in myPolylinesDict['CURVE']:
polyline.drawSWF(myShape, movie, myPen.attributeDict["CURVE"]["lineWidth"], myPen.attributeDict["CURVE"]["lineColor"])
movie.add(myShape)
#draw the global silhouette
if self.settings.globalLineVisible and self.scene.faces:
myShape = shape.next()
movie.remove(myShape)
self.globalSilhouetteRegion.outlineSWF(myShape, movie, self.settings.globalLineWidth, self.settings.globalLineColor)
movie.add(myShape)
def renderSwatch(self, pen = Pen(), backgroundColor = (0.9,0.9,0.9,1), width = 50, height = 15):
"""
Returns a dict of swatches for each linetype/fill of the current pen
"""
attributeList = ["SILHOUETTE", "CREASE", "MESH", "HIDDEN", "CUT", "CURVE"]
factor = 1
swatchDict = dict()
swatchFill = [(0,0), (0,height), (width, height), (width,0) ]
swatchLine = [(0,height/2), (width, height/2)]
myPen = pen
#first, draw the line swatches:
for attribute in attributeList:
swatchDict[attribute] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
context = cairo.Context(swatchDict[attribute])
context.set_source_rgba(backgroundColor[0],
backgroundColor[1],
backgroundColor[2],
backgroundColor[3])
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.fill()
context.set_line_join(myPen.attributeDict[attribute]["lineJoin"])
context.set_line_cap(myPen.attributeDict[attribute]["lineCap"])
context.set_source_rgba(myPen.attributeDict[attribute]["lineColor"].r,
myPen.attributeDict[attribute]["lineColor"].g,
myPen.attributeDict[attribute]["lineColor"].b,
myPen.attributeDict[attribute]["lineColor"].a)
context.set_line_width(myPen.attributeDict[attribute]["lineWidth"])
context.set_dash(myPen.attributeDict[attribute]["lineDash"])
context.new_path()
context.move_to(swatchLine[0][0], swatchLine[0][1])
context.line_to(swatchLine[1][0], swatchLine[1][1])
context.stroke()
#draw the material silhouette:
swatchDict["MATERIAL SILHOUETTE"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
context = cairo.Context(swatchDict["MATERIAL SILHOUETTE"])
context.set_source_rgba(backgroundColor[0],
backgroundColor[1],
backgroundColor[2],
backgroundColor[3])
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.fill()
context.set_line_join(myPen.attributeDict[attribute]["lineJoin"])
context.set_line_cap(myPen.attributeDict[attribute]["lineCap"])
context.set_source_rgba(myPen.attributeDict[attribute]["lineColor"].r,
myPen.attributeDict[attribute]["lineColor"].g,
myPen.attributeDict[attribute]["lineColor"].b,
myPen.attributeDict[attribute]["lineColor"].a)
context.set_line_width(myPen.attributeDict[attribute]["lineWidth"])
context.set_dash(myPen.attributeDict[attribute]["lineDash"])
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.stroke()
#draw the global silhouette:
swatchDict["GLOBAL SILHOUETTE"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
context = cairo.Context(swatchDict["GLOBAL SILHOUETTE"])
context.set_source_rgba(backgroundColor[0],
backgroundColor[1],
backgroundColor[2],
backgroundColor[3])
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.fill()
context.set_line_join(self.settings.globalLineJoin)
context.set_line_cap(self.settings.globalLineCap)
context.set_source_rgba(self.settings.globalLineColor.r,
self.settings.globalLineColor.g,
self.settings.globalLineColor.b,
self.settings.globalLineColor.a)
context.set_line_width(self.settings.globalLineWidth)
context.set_dash(self.settings.globalLineDash)
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.stroke()
#finally, draw the fill
swatchDict["FILL"] = cairo.ImageSurface(cairo.FORMAT_RGB24, int(width*factor), int(height*factor))
context = cairo.Context(swatchDict["FILL"])
context.set_source_rgba(myPen.attributeDict["FILLCOLOR"].r,
myPen.attributeDict["FILLCOLOR"].g,
myPen.attributeDict["FILLCOLOR"].b,
myPen.attributeDict["FILLCOLOR"].a)
context.new_path()
context.move_to(swatchFill[0][0], swatchFill[0][1])
context.line_to(swatchFill[1][0], swatchFill[1][1])
context.line_to(swatchFill[2][0], swatchFill[2][1])
context.line_to(swatchFill[3][0], swatchFill[3][1])
context.close_path()
context.fill()
return swatchDict
class Settings:
"""
This is a container class for what is essentially a dictionary of
material name -> pen
"""
def __init__(self):
self.pens = {} #this is a dict assigning pens to materials
self.materialDefaults = {'DEFAULT':Pen().attributeDict["FILLCOLOR"]}
self.pens['DEFAULT'] = Pen()
self.penStorage = [Pen()]
#Create a debug material for faulty faces
if DEBUG:
self.pens['DEBUG'] = Pen()
self.pens['DEBUG'].attributeDict["FILLCOLOR"] = RGBA(1.0,0.0,0.0,1.0)
self.penStorage.append(self.pens['DEBUG'])
#These are the default global silhouette settings (is this the best place??)
self.globalLineVisible = True
self.globalLineWidth = 1.0
self.globalLineDash = Pen.SOLID
self.globalLineColor = Pen.BLACK
#these are not user-settable at this time!
self.globalLineJoin = Pen.BEVEL
self.globalLineCap = Pen.ROUND
#default file locations - these get set when the program first opens
self.currentSettingsFolder = HOMEDIR
self.currentExportFolder = HOMEDIR
self.currentPen = self.pens['DEFAULT'] #This gets set by the GUI
#these get set in the intro dialogue
self.CULL_BACKFACES = CULL_BACKFACES
self.USE_ALPHA = USE_ALPHA
self.RENDER_ANIMATION = RENDER_ANIMATION
self.AUTO_CREASE = AUTO_CREASE
self.LOAD_PREVIOUS_SETTINGS = LOAD_PREVIOUS_SETTINGS
def loadDefaultSettings( self ):
try:
self.load( HOMEDIR + "default.pog" )
except:
self.currentSettingsFolder = HOMEDIR
self.currentExportFolder = HOMEDIR
def saveDefaultSettings( self ):
self.save( HOMEDIR + "default.pog" )
def setMaterialToPen(materialName,pen):
self.pens[materialName] = pen
def setMaterialToDefault(materialName):
self.pens[materialName] = Pen()
self.pens[materialName].fillColor = self.materialDefaults[materialName]
def setFillColorDefault(materialName):
self.pens[materialName].fillColor = self.materialDefaults[materialName]
def load(self,filename):
myFile = open(filename, mode = 'r')
self.materialDefaults, self.pens, self.penStorage, self.globalLineVisible, self.globalLineWidth, self.globalLineDash,\
self.globalLineColor, self.globalLineCap, self.globalLineJoin = cPickle.load(myFile)
myFile.close()
def save(self,filename):
myFile = open(filename, mode = 'w')
cPickle.dump((self.materialDefaults, self.pens, self.penStorage, self.globalLineVisible, self.globalLineWidth, self.globalLineDash,\
self.globalLineColor, self.globalLineCap, self.globalLineJoin),myFile)
myFile.close()
def loadPen(self,filename):
myFile = open(filename, mode = 'r')
myPen = Pen()
(myPen.attributeDict, myPen.name) = cPickle.load(myFile)
myPen.name = os.path.basename(filename).split('.')[0]
for pen in self.penStorage:
if myPen.name == pen.name:
pen.attributeDict = myPen.attributeDict
myFile.close()
return
self.penStorage.append(myPen)
myFile.close()
def savePen(self,filename,currentPen):
myPen = currentPen
myFile = open(filename, mode = 'w')
cPickle.dump( (myPen.attributeDict, myPen.name),myFile)
myFile.close()