6 Tooltip: 'Vector Rendering Method script'
 
   9 __author__ = "Antonio Ospite"
 
  10 __url__ = ["http://projects.blender.org/projects/vrm"]
 
  11 __version__ = "0.3.beta"
 
  14     Render the scene and save the result in vector format.
 
  17 # ---------------------------------------------------------------------
 
  18 #    Copyright (c) 2006 Antonio Ospite
 
  20 #    This program is free software; you can redistribute it and/or modify
 
  21 #    it under the terms of the GNU General Public License as published by
 
  22 #    the Free Software Foundation; either version 2 of the License, or
 
  23 #    (at your option) any later version.
 
  25 #    This program is distributed in the hope that it will be useful,
 
  26 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
  27 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
  28 #    GNU General Public License for more details.
 
  30 #    You should have received a copy of the GNU General Public License
 
  31 #    along with this program; if not, write to the Free Software
 
  32 #    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
  34 # ---------------------------------------------------------------------
 
  37 #   Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
 
  38 #   Thanks to Nikola Radovanovic, the author of the original VRM script,
 
  39 #       the code you read here has been rewritten _almost_ entirely
 
  40 #       from scratch but Nikola gave me the idea, so I thank him publicly.
 
  42 # ---------------------------------------------------------------------
 
  44 # Things TODO for a next release:
 
  45 #   - FIX the issue with negative scales in object tranformations!
 
  46 #   - Use a better depth sorting algorithm
 
  47 #   - Implement clipping of primitives and do handle object intersections.
 
  48 #     (for now only clipping away whole objects is supported).
 
  49 #   - Review how selections are made (this script uses selection states of
 
  50 #     primitives to represent visibility infos)
 
  51 #   - Use a data structure other than Mesh to represent the 2D image? 
 
  52 #     Think to a way to merge (adjacent) polygons that have the same color.
 
  53 #     Or a way to use paths for silhouettes and contours.
 
  54 #   - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
 
  55 #     not support SMIL for animations)
 
  56 #   - Switch to the Mesh structure, should be considerably faster
 
  57 #     (partially done, but with Mesh we cannot sort faces, yet)
 
  58 #   - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
 
  59 #   - Implement Shading Styles? (partially done, to make more flexible).
 
  60 #   - Add Vector Writers other than SVG.
 
  61 #   - set the background color!
 
  62 #   - Check memory use!!
 
  64 # ---------------------------------------------------------------------
 
  69 #     * First release after code restucturing.
 
  70 #       Now the script offers a useful set of functionalities
 
  71 #       and it can render animations, too.
 
  72 #     * Optimization in Renderer.doEdgeStyle(), build a topology cache
 
  73 #       so to speed up the lookup of adjacent faces of an edge.
 
  75 #     * The SVG output is now SVG 1.0 valid.
 
  76 #       Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
 
  77 #     * Progress indicator during HSR.
 
  78 #     * Initial SWF output support
 
  79 #     * Fixed a bug in the animation code, now the projection matrix is
 
  80 #       recalculated at each frame!
 
  82 # ---------------------------------------------------------------------
 
  85 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
 
  86 from Blender.Mathutils import *
 
  93 # We use a global progress Indicator Object
 
  97 # Some global settings
 
 101     polygons['SHOW'] = True
 
 102     polygons['SHADING'] = 'FLAT'
 
 103     #polygons['HSR'] = 'PAINTER' # 'PAINTER' or 'NEWELL'
 
 104     polygons['HSR'] = 'NEWELL'
 
 105     # Hidden to the user for now
 
 106     polygons['EXPANSION_TRICK'] = True
 
 108     polygons['TOON_LEVELS'] = 2
 
 111     edges['SHOW'] = False
 
 112     edges['SHOW_HIDDEN'] = False
 
 113     edges['STYLE'] = 'MESH' # or SILHOUETTE
 
 115     edges['COLOR'] = [0, 0, 0]
 
 118     output['FORMAT'] = 'SVG'
 
 119     output['ANIMATION'] = False
 
 120     output['JOIN_OBJECTS'] = True
 
 122     #output['FORMAT'] = 'SWF'
 
 123     #output['ANIMATION'] = True
 
 129 def dumpfaces(flist, filename):
 
 130     """Dump a single face to a file.
 
 141     writerobj = SVGVectorWriter(filename)
 
 144     writerobj._printPolygons(m)
 
 150         sys.stderr.write(msg)
 
 153     return (abs(v1[0]-v2[0]) < EPS and 
 
 154             abs(v1[1]-v2[1]) < EPS )
 
 155 by_furthest_z = (lambda f1, f2:
 
 156     cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
 
 171 # ---------------------------------------------------------------------
 
 175 # ---------------------------------------------------------------------
 
 181     """A utility class for HSR processing.
 
 184     def is_nonplanar_quad(face):
 
 185         """Determine if a quad is non-planar.
 
 187         From: http://mathworld.wolfram.com/Coplanar.html
 
 189         Geometric objects lying in a common plane are said to be coplanar.
 
 190         Three noncollinear points determine a plane and so are trivially coplanar.
 
 191         Four points are coplanar iff the volume of the tetrahedron defined by them is
 
 197             | x_4 y_4 z_4 1 | == 0
 
 199         Coplanarity is equivalent to the statement that the pair of lines
 
 200         determined by the four points are not skew, and can be equivalently stated
 
 201         in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
 
 203         An arbitrary number of n points x_1, ..., x_n can be tested for
 
 204         coplanarity by finding the point-plane distances of the points
 
 205         x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
 
 206         and checking if they are all zero.
 
 207         If so, the points are all coplanar.
 
 209         We here check only for 4-point complanarity.
 
 215             print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
 
 219             # three points must be complanar
 
 222             x1 = Vector(face[0].co)
 
 223             x2 = Vector(face[1].co)
 
 224             x3 = Vector(face[2].co)
 
 225             x4 = Vector(face[3].co)
 
 227             v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
 
 233     is_nonplanar_quad = staticmethod(is_nonplanar_quad)
 
 235     def pointInPolygon(poly, v):
 
 238     pointInPolygon = staticmethod(pointInPolygon)
 
 240     def edgeIntersection(s1, s2, do_perturbate=False):
 
 242         (x1, y1) = s1[0].co[0], s1[0].co[1]
 
 243         (x2, y2) = s1[1].co[0], s1[1].co[1]
 
 245         (x3, y3) = s2[0].co[0], s2[0].co[1]
 
 246         (x4, y4) = s2[1].co[0], s2[1].co[1]
 
 254         # calculate delta values (vector components)
 
 263         C = dy2 * dx1 - dx2 * dy1 #  /* cross product */
 
 264         if C == 0:  #/* parallel */
 
 267         dx3 = x1 - x3 # /* combined origin offset vector */
 
 270         a1 = (dy3 * dx2 - dx3 * dy2) / C;
 
 271         a2 = (dy3 * dx1 - dx3 * dy1) / C;
 
 273         # check for degeneracies
 
 275         #print_debug(str(a1)+"\n")
 
 276         #print_debug(str(a2)+"\n\n")
 
 278         if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
 
 279             # Intersection on boundaries, we consider the point external?
 
 282         elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): #  /* lines cross */
 
 288             return (NMesh.Vert(x, y, z), a1, a2)
 
 291             # lines have intersections but not those segments
 
 294     edgeIntersection = staticmethod(edgeIntersection)
 
 296     def isVertInside(self, v):
 
 300         # Create point at infinity
 
 301         point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
 
 303         for i in range(len(self.v)):
 
 304             s1 = (point_at_infinity, v)
 
 305             s2 = (self.v[i-1], self.v[i])
 
 307             if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
 
 310             if HSR.edgeIntersection(s1, s2, do_perturbate=False):
 
 314         if winding_number % 2 == 0 :
 
 321     isVertInside = staticmethod(isVertInside)
 
 323     def projectionsOverlap(f1, f2):
 
 324         """ If you have nonconvex, but still simple polygons, an acceptable method
 
 325         is to iterate over all vertices and perform the Point-in-polygon test[1].
 
 326         The advantage of this method is that you can compute the exact
 
 327         intersection point and collision normal that you will need to simulate
 
 328         collision. When you have the point that lies inside the other polygon, you
 
 329         just iterate over all edges of the second polygon again and look for edge
 
 330         intersections. Note that this method detects collsion when it already
 
 331         happens. This algorithm is fast enough to perform it hundreds of times per
 
 334         for i in range(len(f1.v)):
 
 337             # If a point of f1 in inside f2, there is an overlap!
 
 339             if HSR.isVertInside(f2, v1):
 
 342             # If not the polygon can be ovelap as well, so we check for
 
 343             # intersection between an edge of f1 and all the edges of f2
 
 347             for j in range(len(f2.v)):
 
 354                 intrs = HSR.edgeIntersection(e1, e2)
 
 356                     #print_debug(str(v0.co) + " " + str(v1.co) + " " +
 
 357                     #        str(v2.co) + " " + str(v3.co) )
 
 358                     #print_debug("\nIntersection\n")
 
 364     projectionsOverlap = staticmethod(projectionsOverlap)
 
 366     def midpoint(p1, p2):
 
 367         """Return the midpoint of two vertices.
 
 369         m = MidpointVecs(Vector(p1), Vector(p2))
 
 370         mv = NMesh.Vert(m[0], m[1], m[2])
 
 374     midpoint = staticmethod(midpoint)
 
 376     def facesplit(P, Q, facelist, nmesh):
 
 377         """Split P or Q according to the strategy illustrated in the Newell's
 
 381         by_furthest_z = (lambda f1, f2:
 
 382                 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
 
 385         # Choose if split P on Q plane or vice-versa
 
 389             d = HSR.Distance(Vector(Pi), Q)
 
 392         pIntersectQ = (n != len(P))
 
 396             d = HSR.Distance(Vector(Qi), P)
 
 399         qIntersectP = (n != len(Q))
 
 403         # 1. If parts of P lie in both half-spaces of Q
 
 404         # then splice P in two with the plane of Q
 
 410             newfaces = HSR.splitOn(plane, f)
 
 412         # 2. Else if parts of Q lie in both half-space of P
 
 413         # then splice Q in two with the plane of P
 
 414         if qIntersectP and newfaces == None:
 
 419             newfaces = HSR.splitOn(plane, f)
 
 422         # 3. Else slice P in half through the mid-point of
 
 423         # the longest pair of opposite sides
 
 426             print "We ignore P..."
 
 433             #    v1 = midpoint(f[0], f[1])
 
 434             #    v2 = midpoint(f[1], f[2])
 
 436             #    v1 = midpoint(f[0], f[1])
 
 437             #    v2 = midpoint(f[2], f[3])
 
 438             #vec3 = (Vector(v2)+10*Vector(f.normal))
 
 440             #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
 
 442             #plane = NMesh.Face([v1, v2, v3])
 
 444             #newfaces = splitOn(plane, f)
 
 448             print "Big FAT problem, we weren't able to split POLYGONS!"
 
 454             #    if v not in plane and v in nmesh.verts:
 
 455             #        nmesh.verts.remove(v)
 
 460                 nf.col = [f.col[0]] * len(nf.v)
 
 465                     nmesh.verts.append(v)
 
 466                 # insert pieces in the list
 
 471         # and resort the faces
 
 472         facelist.sort(by_furthest_z)
 
 473         facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
 
 476         #print [ f.smooth for f in facelist ]
 
 480     facesplit = staticmethod(facesplit)
 
 482     def isOnSegment(v1, v2, p, extremes_internal=False):
 
 483         """Check if point p is in segment v1v2.
 
 489         # Should we consider extreme points as internal ?
 
 491         # if p == v1 or p == v2:
 
 492         if l1 < EPS or l2 < EPS:
 
 493             return extremes_internal
 
 497         # if the sum of l1 and l2 is circa l, then the point is on segment,
 
 498         if abs(l - (l1+l2)) < EPS:
 
 503     isOnSegment = staticmethod(isOnSegment)
 
 505     def Distance(point, face):
 
 506         """ Calculate the distance between a point and a face.
 
 508         An alternative but more expensive method can be:
 
 510             ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
 
 511                     Vector(face.no), Vector(point), 0)
 
 513             d = Vector(ip - point).length
 
 515         See: http://mathworld.wolfram.com/Point-PlaneDistance.html
 
 519         plNormal = Vector(face.no)
 
 520         plVert0 = Vector(face.v[0])
 
 522         d = (plVert0 * plNormal) - (p * plNormal)
 
 524         #d = plNormal * (plVert0 - p)
 
 526         #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
 
 530     Distance = staticmethod(Distance)
 
 534         # make one or two new faces based on a list of vertex-indices
 
 563     makeFaces = staticmethod(makeFaces)
 
 566         """Split P using the plane of Q.
 
 567         Logic taken from the knife.py python script
 
 570         # Check if P and Q are parallel
 
 571         u = CrossVecs(Vector(Q.no),Vector(P.no))
 
 577             print "PARALLEL planes!!"
 
 581         # The final aim is to find the intersection line between P
 
 582         # and the plane of Q, and split P along this line
 
 586         # Calculate point-plane Distance between vertices of P and plane Q
 
 588         for i in range(0, nP):
 
 589             d.append(HSR.Distance(P.v[i], Q))
 
 602             #print "d0:", d0, "d1:", d1
 
 604             # if the vertex lies in the cutplane                        
 
 606                 #print "d1 On cutplane"
 
 607                 posVertList.append(V1)
 
 608                 negVertList.append(V1)
 
 610                 # if the previous vertex lies in cutplane
 
 612                     #print "d0 on Cutplane"
 
 614                         #print "d1 on positive Halfspace"
 
 615                         posVertList.append(V1)
 
 617                         #print "d1 on negative Halfspace"
 
 618                         negVertList.append(V1)
 
 620                     # if they are on the same side of the plane
 
 622                         #print "On the same half-space"
 
 624                             #print "d1 on positive Halfspace"
 
 625                             posVertList.append(V1)
 
 627                             #print "d1 on negative Halfspace"
 
 628                             negVertList.append(V1)
 
 630                     # the vertices are not on the same side of the plane, so we have an intersection
 
 632                         #print "Intersection"
 
 634                         e = Vector(V0), Vector(V1)
 
 635                         tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
 
 637                         inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
 
 642                         #print "Intersection", inters
 
 644                         nv = NMesh.Vert(inters[0], inters[1], inters[2])
 
 645                         newVertList.append(nv)
 
 647                         posVertList.append(nv)
 
 648                         negVertList.append(nv)
 
 651                             posVertList.append(V1)
 
 653                             negVertList.append(V1)
 
 657         posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
 
 658         negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
 
 661         # If vertex are all on the same half-space, return
 
 662         #if len(posVertList) < 3:
 
 663         #    print "Problem, we created a face with less that 3 verteices??"
 
 665         #if len(negVertList) < 3:
 
 666         #    print "Problem, we created a face with less that 3 verteices??"
 
 669         if len(posVertList) < 3 or len(negVertList) < 3:
 
 670             print "RETURN NONE, SURE???"
 
 674         newfaces = HSR.addNewFaces(posVertList, negVertList)
 
 678     splitOn = staticmethod(splitOn)
 
 680     def addNewFaces(posVertList, negVertList):
 
 681         # Create new faces resulting from the split
 
 683         if len(posVertList) or len(negVertList):
 
 685             #newfaces = [posVertList] + [negVertList]
 
 686             newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
 
 687                     [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
 
 691                     outfaces += HSR.makeFaces(nf)
 
 696     addNewFaces = staticmethod(addNewFaces)
 
 699 # ---------------------------------------------------------------------
 
 701 ## Mesh Utility class
 
 703 # ---------------------------------------------------------------------
 
 707     def buildEdgeFaceUsersCache(me):
 
 709         Takes a mesh and returns a list aligned with the meshes edges.
 
 710         Each item is a list of the faces that use the edge
 
 711         would be the equiv for having ed.face_users as a property
 
 713         Taken from .blender/scripts/bpymodules/BPyMesh.py,
 
 714         thanks to ideasman_42.
 
 717         def sorted_edge_indicies(ed):
 
 725         face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
 
 727             fvi= [v.index for v in f.v]# face vert idx's
 
 728             for i in xrange(len(f)):
 
 735                 face_edges_dict[i1,i2][1].append(f)
 
 737         face_edges= [None] * len(me.edges)
 
 738         for ed_index, ed_faces in face_edges_dict.itervalues():
 
 739             face_edges[ed_index]= ed_faces
 
 743     def isMeshEdge(adjacent_faces):
 
 746         A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
 
 747         Note: if the edge has no adjacent faces we want to show it as well,
 
 748         useful for "edge only" portion of objects.
 
 751         if len(adjacent_faces) == 0:
 
 754         selected_faces = [f for f in adjacent_faces if f.sel]
 
 756         if len(selected_faces) != 0:
 
 761     def isSilhouetteEdge(adjacent_faces):
 
 762         """Silhuette selection rule.
 
 764         An edge is a silhuette edge if it is shared by two faces with
 
 765         different selection status or if it is a boundary edge of a selected
 
 769         if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
 
 770             (len(adjacent_faces) == 2 and
 
 771                 adjacent_faces[0].sel != adjacent_faces[1].sel)
 
 777     buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
 
 778     isMeshEdge = staticmethod(isMeshEdge)
 
 779     isSilhouetteEdge = staticmethod(isSilhouetteEdge)
 
 782 # ---------------------------------------------------------------------
 
 784 ## Shading Utility class
 
 786 # ---------------------------------------------------------------------
 
 792     def toonShadingMapSetup():
 
 793         levels = config.polygons['TOON_LEVELS']
 
 795         texels = 2*levels - 1
 
 796         tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
 
 802         shademap = ShadingUtils.shademap
 
 805             shademap = ShadingUtils.toonShadingMapSetup()
 
 808         for i in xrange(0, len(shademap)-1):
 
 809             pivot = (shademap[i]+shademap[i+1])/2.0
 
 814             if v < shademap[i+1]:
 
 819     toonShadingMapSetup = staticmethod(toonShadingMapSetup)
 
 820     toonShading = staticmethod(toonShading)
 
 823 # ---------------------------------------------------------------------
 
 825 ## Projections classes
 
 827 # ---------------------------------------------------------------------
 
 830     """Calculate the projection of an object given the camera.
 
 832     A projector is useful to so some per-object transformation to obtain the
 
 833     projection of an object given the camera.
 
 835     The main method is #doProjection# see the method description for the
 
 839     def __init__(self, cameraObj, canvasRatio):
 
 840         """Calculate the projection matrix.
 
 842         The projection matrix depends, in this case, on the camera settings.
 
 843         TAKE CARE: This projector expects vertices in World Coordinates!
 
 846         camera = cameraObj.getData()
 
 848         aspect = float(canvasRatio[0])/float(canvasRatio[1])
 
 849         near = camera.clipStart
 
 852         scale = float(camera.scale)
 
 854         fovy = atan(0.5/aspect/(camera.lens/32))
 
 855         fovy = fovy * 360.0/pi
 
 857         # What projection do we want?
 
 859             mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) 
 
 860         elif camera.type == 1:
 
 861             mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) 
 
 863         # View transformation
 
 864         cam = Matrix(cameraObj.getInverseMatrix())
 
 869         self.projectionMatrix = mP
 
 875     def doProjection(self, v):
 
 876         """Project the point on the view plane.
 
 878         Given a vertex calculate the projection using the current projection
 
 882         # Note that we have to work on the vertex using homogeneous coordinates
 
 883         # From blender 2.42+ we don't need to resize the vector to be 4d
 
 884         # when applying a 4x4 matrix, but we do that anyway since we need the
 
 885         # 4th coordinate later
 
 886         p = self.projectionMatrix * Vector(v).resize4D()
 
 888         # Perspective division
 
 905     def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
 
 906         """Return a perspective projection matrix.
 
 909         top = near * tan(fovy * pi / 360.0)
 
 913         x = (2.0 * near) / (right-left)
 
 914         y = (2.0 * near) / (top-bottom)
 
 915         a = (right+left) / (right-left)
 
 916         b = (top+bottom) / (top - bottom)
 
 917         c = - ((far+near) / (far-near))
 
 918         d = - ((2*far*near)/(far-near))
 
 924                 [0.0, 0.0, -1.0,    0.0])
 
 928     def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
 
 929         """Return an orthogonal projection matrix.
 
 932         # The 11 in the formula was found emiprically
 
 933         top = near * tan(fovy * pi / 360.0) * (scale * 11)
 
 935         left = bottom * aspect
 
 940         tx = -((right+left)/rl)
 
 941         ty = -((top+bottom)/tb)
 
 945                 [2.0/rl, 0.0,    0.0,     tx],
 
 946                 [0.0,    2.0/tb, 0.0,     ty],
 
 947                 [0.0,    0.0,    2.0/fn,  tz],
 
 948                 [0.0,    0.0,    0.0,    1.0])
 
 953 # ---------------------------------------------------------------------
 
 955 ## Progress Indicator
 
 957 # ---------------------------------------------------------------------
 
 960     """A model for a progress indicator.
 
 962     Do the progress calculation calculation and
 
 963     the view independent stuff of a progress indicator.
 
 965     def __init__(self, steps=0):
 
 971     def setSteps(self, steps):
 
 972         """Set the number of steps of the activity wich we want to track.
 
 979     def setName(self, name):
 
 980         """Set the name of the activity wich we want to track.
 
 987     def getProgress(self):
 
 995         """Update the model, call this method when one step is completed.
 
 997         if self.progress == 100:
 
1001         self.progress = ( float(self.completed) / float(self.steps) ) * 100
 
1002         self.progress = int(self.progress)
 
1007 class ProgressIndicator:
 
1008     """An abstraction of a View for the Progress Model
 
1012         # Use a refresh rate so we do not show the progress at
 
1013         # every update, but every 'self.refresh_rate' times.
 
1014         self.refresh_rate = 10
 
1015         self.shows_counter = 0
 
1019         self.progressModel = None
 
1021     def setQuiet(self, value):
 
1024     def setActivity(self, name, steps):
 
1025         """Initialize the Model.
 
1027         In a future version (with subactivities-progress support) this method
 
1028         could only set the current activity.
 
1030         self.progressModel = Progress()
 
1031         self.progressModel.setName(name)
 
1032         self.progressModel.setSteps(steps)
 
1034     def getActivity(self):
 
1035         return self.progressModel
 
1038         """Update the model and show the actual progress.
 
1040         assert(self.progressModel)
 
1042         if self.progressModel.update():
 
1046             self.show(self.progressModel.getProgress(),
 
1047                     self.progressModel.getName())
 
1049         # We return always True here so we can call the update() method also
 
1050         # from lambda funcs (putting the call in logical AND with other ops)
 
1053     def show(self, progress, name=""):
 
1054         self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
 
1055         if self.shows_counter != 0:
 
1059             self.shows_counter = -1
 
1062 class ConsoleProgressIndicator(ProgressIndicator):
 
1063     """Show a progress bar on stderr, a la wget.
 
1066         ProgressIndicator.__init__(self)
 
1068         self.swirl_chars = ["-", "\\", "|", "/"]
 
1069         self.swirl_count = -1
 
1071     def show(self, progress, name):
 
1072         ProgressIndicator.show(self, progress, name)
 
1075         bar_progress = int( (progress/100.0) * bar_length )
 
1076         bar = ("=" * bar_progress).ljust(bar_length)
 
1078         self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
 
1079         swirl_char = self.swirl_chars[self.swirl_count]
 
1081         progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
 
1083         sys.stderr.write(progress_bar+"\r")
 
1085             sys.stderr.write("\n")
 
1088 class GraphicalProgressIndicator(ProgressIndicator):
 
1089     """Interface to the Blender.Window.DrawProgressBar() method.
 
1092         ProgressIndicator.__init__(self)
 
1094         #self.swirl_chars = ["-", "\\", "|", "/"]
 
1095         # We have to use letters with the same width, for now!
 
1096         # Blender progress bar considers the font widths when
 
1097         # calculating the progress bar width.
 
1098         self.swirl_chars = ["\\", "/"]
 
1099         self.swirl_count = -1
 
1101     def show(self, progress, name):
 
1102         ProgressIndicator.show(self, progress)
 
1104         self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
 
1105         swirl_char = self.swirl_chars[self.swirl_count]
 
1107         progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
 
1109         # Finally draw  the Progress Bar
 
1110         Window.WaitCursor(1) # Maybe we can move that call in the constructor?
 
1111         Window.DrawProgressBar(progress/100.0, progress_text)
 
1114             Window.DrawProgressBar(1, progress_text)
 
1115             Window.WaitCursor(0)
 
1119 # ---------------------------------------------------------------------
 
1121 ## 2D Object representation class
 
1123 # ---------------------------------------------------------------------
 
1125 # TODO: a class to represent the needed properties of a 2D vector image
 
1126 # For now just using a [N]Mesh structure.
 
1129 # ---------------------------------------------------------------------
 
1131 ## Vector Drawing Classes
 
1133 # ---------------------------------------------------------------------
 
1139     A class for printing output in a vectorial format.
 
1141     Given a 2D representation of the 3D scene the class is responsible to
 
1142     write it is a vector format.
 
1144     Every subclasses of VectorWriter must have at last the following public
 
1148         - printCanvas(self, scene,
 
1149             doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
 
1152     def __init__(self, fileName):
 
1153         """Set the output file name and other properties"""
 
1155         self.outputFileName = fileName
 
1158         context = Scene.GetCurrent().getRenderingContext()
 
1159         self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
 
1163         self.animation = False
 
1170     def open(self, startFrame=1, endFrame=1):
 
1171         if startFrame != endFrame:
 
1172             self.startFrame = startFrame
 
1173             self.endFrame = endFrame
 
1174             self.animation = True
 
1176         self.file = open(self.outputFileName, "w")
 
1177         print "Outputting to: ", self.outputFileName
 
1186     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
 
1187             showHiddenEdges=False):
 
1188         """This is the interface for the needed printing routine.
 
1195 class SVGVectorWriter(VectorWriter):
 
1196     """A concrete class for writing SVG output.
 
1199     def __init__(self, fileName):
 
1200         """Simply call the parent Contructor.
 
1202         VectorWriter.__init__(self, fileName)
 
1209     def open(self, startFrame=1, endFrame=1):
 
1210         """Do some initialization operations.
 
1212         VectorWriter.open(self, startFrame, endFrame)
 
1216         """Do some finalization operation.
 
1220         # remember to call the close method of the parent
 
1221         VectorWriter.close(self)
 
1224     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
 
1225             showHiddenEdges=False):
 
1226         """Convert the scene representation to SVG.
 
1229         Objects = scene.getChildren()
 
1231         context = scene.getRenderingContext()
 
1232         framenumber = context.currentFrame()
 
1235             framestyle = "display:none"
 
1237             framestyle = "display:block"
 
1239         # Assign an id to this group so we can set properties on it using DOM
 
1240         self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
 
1241                 (framenumber, framestyle) )
 
1246             if(obj.getType() != 'Mesh'):
 
1249             self.file.write("<g id=\"%s\">\n" % obj.getName())
 
1251             mesh = obj.getData(mesh=1)
 
1254                 self._printPolygons(mesh)
 
1257                 self._printEdges(mesh, showHiddenEdges)
 
1259             self.file.write("</g>\n")
 
1261         self.file.write("</g>\n")
 
1268     def _calcCanvasCoord(self, v):
 
1269         """Convert vertex in scene coordinates to canvas coordinates.
 
1272         pt = Vector([0, 0, 0])
 
1274         mW = float(self.canvasSize[0])/2.0
 
1275         mH = float(self.canvasSize[1])/2.0
 
1277         # rescale to canvas size
 
1278         pt[0] = v.co[0]*mW + mW
 
1279         pt[1] = v.co[1]*mH + mH
 
1282         # For now we want (0,0) in the top-left corner of the canvas.
 
1283         # Mirror and translate along y
 
1285         pt[1] += self.canvasSize[1]
 
1289     def _printHeader(self):
 
1290         """Print SVG header."""
 
1292         self.file.write("<?xml version=\"1.0\"?>\n")
 
1293         self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
 
1294         self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
 
1295         self.file.write("<svg version=\"1.0\"\n")
 
1296         self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
 
1297         self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
 
1302             self.file.write("""\n<script type="text/javascript"><![CDATA[
 
1303             globalStartFrame=%d;
 
1306             /* FIXME: Use 1000 as interval as lower values gives problems */
 
1307             timerID = setInterval("NextFrame()", 1000);
 
1308             globalFrameCounter=%d;
 
1310             function NextFrame()
 
1312               currentElement  = document.getElementById('frame'+globalFrameCounter)
 
1313               previousElement = document.getElementById('frame'+(globalFrameCounter-1))
 
1315               if (!currentElement)
 
1320               if (globalFrameCounter > globalEndFrame)
 
1322                 clearInterval(timerID)
 
1328                     previousElement.style.display="none";
 
1330                 currentElement.style.display="block";
 
1331                 globalFrameCounter++;
 
1335             \n""" % (self.startFrame, self.endFrame, self.startFrame) )
 
1337     def _printFooter(self):
 
1338         """Print the SVG footer."""
 
1340         self.file.write("\n</svg>\n")
 
1342     def _printPolygons(self, mesh): 
 
1343         """Print the selected (visible) polygons.
 
1346         if len(mesh.faces) == 0:
 
1349         self.file.write("<g>\n")
 
1351         for face in mesh.faces:
 
1355             self.file.write("<path d=\"")
 
1357             #p = self._calcCanvasCoord(face.verts[0])
 
1358             p = self._calcCanvasCoord(face.v[0])
 
1359             self.file.write("M %g,%g L " % (p[0], p[1]))
 
1361             for v in face.v[1:]:
 
1362                 p = self._calcCanvasCoord(v)
 
1363                 self.file.write("%g,%g " % (p[0], p[1]))
 
1365             # get rid of the last blank space, just cosmetics here.
 
1366             self.file.seek(-1, 1) 
 
1367             self.file.write(" z\"\n")
 
1369             # take as face color the first vertex color
 
1372                 color = [fcol.r, fcol.g, fcol.b, fcol.a]
 
1374                 color = [255, 255, 255, 255]
 
1376             # Convert the color to the #RRGGBB form
 
1377             str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
 
1379             # Handle transparent polygons
 
1382                 opacity = float(color[3])/255.0
 
1383                 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
 
1384                 #opacity_string = "opacity: %g;" % (opacity)
 
1386             self.file.write("\tstyle=\"fill:" + str_col + ";")
 
1387             self.file.write(opacity_string)
 
1389             # use the stroke property to alleviate the "adjacent edges" problem,
 
1390             # we simulate polygon expansion using borders,
 
1391             # see http://www.antigrain.com/svg/index.html for more info
 
1394             # EXPANSION TRICK is not that useful where there is transparency
 
1395             if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
 
1396                 # str_col = "#000000" # For debug
 
1397                 self.file.write(" stroke:%s;\n" % str_col)
 
1398                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
 
1399                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
 
1401             self.file.write("\"/>\n")
 
1403         self.file.write("</g>\n")
 
1405     def _printEdges(self, mesh, showHiddenEdges=False):
 
1406         """Print the wireframe using mesh edges.
 
1409         stroke_width = config.edges['WIDTH']
 
1410         stroke_col = config.edges['COLOR']
 
1412         self.file.write("<g>\n")
 
1414         for e in mesh.edges:
 
1416             hidden_stroke_style = ""
 
1419                 if showHiddenEdges == False:
 
1422                     hidden_stroke_style = ";\n stroke-dasharray:3, 3"
 
1424             p1 = self._calcCanvasCoord(e.v1)
 
1425             p2 = self._calcCanvasCoord(e.v2)
 
1427             self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
 
1428                     % ( p1[0], p1[1], p2[0], p2[1] ) )
 
1429             self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
 
1430             self.file.write(" stroke-width:"+str(stroke_width)+";\n")
 
1431             self.file.write(" stroke-linecap:round;stroke-linejoin:round")
 
1432             self.file.write(hidden_stroke_style)
 
1433             self.file.write("\"/>\n")
 
1435         self.file.write("</g>\n")
 
1444     SWFSupported = False
 
1446 class SWFVectorWriter(VectorWriter):
 
1447     """A concrete class for writing SWF output.
 
1450     def __init__(self, fileName):
 
1451         """Simply call the parent Contructor.
 
1453         VectorWriter.__init__(self, fileName)
 
1463     def open(self, startFrame=1, endFrame=1):
 
1464         """Do some initialization operations.
 
1466         VectorWriter.open(self, startFrame, endFrame)
 
1467         self.movie = SWFMovie()
 
1468         self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
 
1470         self.movie.setRate(25)
 
1471         numframes = endFrame - startFrame + 1
 
1472         self.movie.setFrames(numframes)
 
1475         """Do some finalization operation.
 
1477         self.movie.save(self.outputFileName)
 
1479         # remember to call the close method of the parent
 
1480         VectorWriter.close(self)
 
1482     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
 
1483             showHiddenEdges=False):
 
1484         """Convert the scene representation to SVG.
 
1486         context = scene.getRenderingContext()
 
1487         framenumber = context.currentFrame()
 
1489         Objects = scene.getChildren()
 
1492             self.movie.remove(self.sprite)
 
1494         sprite = SWFSprite()
 
1498             if(obj.getType() != 'Mesh'):
 
1501             mesh = obj.getData(mesh=1)
 
1504                 self._printPolygons(mesh, sprite)
 
1507                 self._printEdges(mesh, sprite, showHiddenEdges)
 
1510         i = self.movie.add(sprite)
 
1511         # Remove the instance the next time
 
1514             self.movie.nextFrame()
 
1521     def _calcCanvasCoord(self, v):
 
1522         """Convert vertex in scene coordinates to canvas coordinates.
 
1525         pt = Vector([0, 0, 0])
 
1527         mW = float(self.canvasSize[0])/2.0
 
1528         mH = float(self.canvasSize[1])/2.0
 
1530         # rescale to canvas size
 
1531         pt[0] = v.co[0]*mW + mW
 
1532         pt[1] = v.co[1]*mH + mH
 
1535         # For now we want (0,0) in the top-left corner of the canvas.
 
1536         # Mirror and translate along y
 
1538         pt[1] += self.canvasSize[1]
 
1542     def _printPolygons(self, mesh, sprite): 
 
1543         """Print the selected (visible) polygons.
 
1546         if len(mesh.faces) == 0:
 
1549         for face in mesh.faces:
 
1555                 color = [fcol.r, fcol.g, fcol.b, fcol.a]
 
1557                 color = [255, 255, 255, 255]
 
1560             f = s.addFill(color[0], color[1], color[2], color[3])
 
1563             # The starting point of the shape
 
1564             p0 = self._calcCanvasCoord(face.verts[0])
 
1565             s.movePenTo(p0[0], p0[1])
 
1568             for v in face.verts[1:]:
 
1569                 p = self._calcCanvasCoord(v)
 
1570                 s.drawLineTo(p[0], p[1])
 
1573             s.drawLineTo(p0[0], p0[1])
 
1579             # use the stroke property to alleviate the "adjacent edges" problem,
 
1580             # we simulate polygon expansion using borders,
 
1581             # see http://www.antigrain.com/svg/index.html for more info
 
1584             # EXPANSION TRICK is not that useful where there is transparency
 
1585             if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
 
1586                 # str_col = "#000000" # For debug
 
1587                 self.file.write(" stroke:%s;\n" % str_col)
 
1588                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
 
1589                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
 
1593     def _printEdges(self, mesh, sprite, showHiddenEdges=False):
 
1594         """Print the wireframe using mesh edges.
 
1597         stroke_width = config.edges['WIDTH']
 
1598         stroke_col = config.edges['COLOR']
 
1602         for e in mesh.edges:
 
1604             #Next, we set the line width and color for our shape.
 
1605             s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
 
1609                 if showHiddenEdges == False:
 
1612                     # SWF does not support dashed lines natively, so -for now-
 
1613                     # draw hidden lines thinner and half-trasparent
 
1614                     s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
 
1617             p1 = self._calcCanvasCoord(e.v1)
 
1618             p2 = self._calcCanvasCoord(e.v2)
 
1620             # FIXME: this is just a qorkaround, remove that after the
 
1621             # implementation of propoer Viewport clipping
 
1622             if abs(p1[0]) < 3000 and abs(p2[0]) < 3000 and abs(p1[1]) < 3000 and abs(p1[2]) < 3000:
 
1623                 s.movePenTo(p1[0], p1[1])
 
1624                 s.drawLineTo(p2[0], p2[1])
 
1632 # ---------------------------------------------------------------------
 
1634 ## Rendering Classes
 
1636 # ---------------------------------------------------------------------
 
1638 # A dictionary to collect different shading style methods
 
1639 shadingStyles = dict()
 
1640 shadingStyles['FLAT'] = None
 
1641 shadingStyles['TOON'] = None
 
1643 # A dictionary to collect different edge style methods
 
1645 edgeStyles['MESH'] = MeshUtils.isMeshEdge
 
1646 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
 
1648 # A dictionary to collect the supported output formats
 
1649 outputWriters = dict()
 
1650 outputWriters['SVG'] = SVGVectorWriter
 
1652     outputWriters['SWF'] = SWFVectorWriter
 
1656     """Render a scene viewed from the active camera.
 
1658     This class is responsible of the rendering process, transformation and
 
1659     projection of the objects in the scene are invoked by the renderer.
 
1661     The rendering is done using the active camera for the current scene.
 
1665         """Make the rendering process only for the current scene by default.
 
1667         We will work on a copy of the scene, to be sure that the current scene do
 
1668         not get modified in any way.
 
1671         # Render the current Scene, this should be a READ-ONLY property
 
1672         self._SCENE = Scene.GetCurrent()
 
1674         # Use the aspect ratio of the scene rendering context
 
1675         context = self._SCENE.getRenderingContext()
 
1677         aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
 
1678         self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
 
1679                             float(context.aspectRatioY())
 
1682         # Render from the currently active camera 
 
1683         self.cameraObj = self._SCENE.getCurrentCamera()
 
1685         # Get the list of lighting sources
 
1686         obj_lst = self._SCENE.getChildren()
 
1687         self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
 
1689         # When there are no lights we use a default lighting source
 
1690         # that have the same position of the camera
 
1691         if len(self.lights) == 0:
 
1692             l = Lamp.New('Lamp')
 
1693             lobj = Object.New('Lamp')
 
1694             lobj.loc = self.cameraObj.loc
 
1696             self.lights.append(lobj)
 
1703     def doRendering(self, outputWriter, animation=False):
 
1704         """Render picture or animation and write it out.
 
1707             - a Vector writer object that will be used to output the result.
 
1708             - a flag to tell if we want to render an animation or only the
 
1712         context = self._SCENE.getRenderingContext()
 
1713         origCurrentFrame = context.currentFrame()
 
1715         # Handle the animation case
 
1717             startFrame = origCurrentFrame
 
1718             endFrame = startFrame
 
1721             startFrame = context.startFrame()
 
1722             endFrame = context.endFrame()
 
1723             outputWriter.open(startFrame, endFrame)
 
1725         # Do the rendering process frame by frame
 
1726         print "Start Rendering of %d frames" % (endFrame-startFrame)
 
1727         for f in xrange(startFrame, endFrame+1):
 
1728             print "\n\nFrame: %d" % f
 
1729             context.currentFrame(f)
 
1731             # Use some temporary workspace, a full copy of the scene
 
1732             inputScene = self._SCENE.copy(2)
 
1733             # And Set our camera accordingly
 
1734             self.cameraObj = inputScene.getCurrentCamera()
 
1736             # Get a projector for this camera.
 
1737             # NOTE: the projector wants object in world coordinates,
 
1738             # so we should remember to apply modelview transformations
 
1739             # _before_ we do projection transformations.
 
1740             self.proj = Projector(self.cameraObj, self.canvasRatio)
 
1743                 renderedScene = self.doRenderScene(inputScene)
 
1745                 print "There was an error! Aborting."
 
1747                 print traceback.print_exc()
 
1749                 self._SCENE.makeCurrent()
 
1750                 Scene.unlink(inputScene)
 
1754             outputWriter.printCanvas(renderedScene,
 
1755                     doPrintPolygons = config.polygons['SHOW'],
 
1756                     doPrintEdges    = config.edges['SHOW'],
 
1757                     showHiddenEdges = config.edges['SHOW_HIDDEN'])
 
1759             # delete the rendered scene
 
1760             self._SCENE.makeCurrent()
 
1761             Scene.unlink(renderedScene)
 
1764         outputWriter.close()
 
1766         context.currentFrame(origCurrentFrame)
 
1769     def doRenderScene(self, workScene):
 
1770         """Control the rendering process.
 
1772         Here we control the entire rendering process invoking the operation
 
1773         needed to transform and project the 3D scene in two dimensions.
 
1776         # global processing of the scene
 
1778         self._doSceneClipping(workScene)
 
1780         self._doConvertGeometricObjsToMesh(workScene)
 
1782         if config.output['JOIN_OBJECTS']:
 
1783             self._joinMeshObjectsInScene(workScene)
 
1785         self._doSceneDepthSorting(workScene)
 
1787         # Per object activities
 
1789         Objects = workScene.getChildren()
 
1790         print "Total Objects: %d" % len(Objects)
 
1791         for i,obj in enumerate(Objects):
 
1793             print "Rendering Object: %d" % i
 
1795             if obj.getType() != 'Mesh':
 
1796                 print "Only Mesh supported! - Skipping type:", obj.getType()
 
1799             print "Rendering: ", obj.getName()
 
1801             mesh = obj.getData(mesh=1)
 
1803             self._doModelingTransformation(mesh, obj.matrix)
 
1805             self._doBackFaceCulling(mesh)
 
1808             # When doing HSR with NEWELL we may want to flip all normals
 
1810             if config.polygons['HSR'] == "NEWELL":
 
1811                 for f in mesh.faces:
 
1814                 for f in mesh.faces:
 
1817             self._doLighting(mesh)
 
1819             # Do "projection" now so we perform further processing
 
1820             # in Normalized View Coordinates
 
1821             self._doProjection(mesh, self.proj)
 
1823             self._doViewFrustumClipping(mesh)
 
1825             self._doHiddenSurfaceRemoval(mesh)
 
1827             self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
 
1829             # Update the object data, important! :)
 
1841     def _getObjPosition(self, obj):
 
1842         """Return the obj position in World coordinates.
 
1844         return obj.matrix.translationPart()
 
1846     def _cameraViewVector(self):
 
1847         """Get the View Direction form the camera matrix.
 
1849         return Vector(self.cameraObj.matrix[2]).resize3D()
 
1854     def _isFaceVisible(self, face):
 
1855         """Determine if a face of an object is visible from the current camera.
 
1857         The view vector is calculated from the camera location and one of the
 
1858         vertices of the face (expressed in World coordinates, after applying
 
1859         modelview transformations).
 
1861         After those transformations we determine if a face is visible by
 
1862         computing the angle between the face normal and the view vector, this
 
1863         angle has to be between -90 and 90 degrees for the face to be visible.
 
1864         This corresponds somehow to the dot product between the two, if it
 
1865         results > 0 then the face is visible.
 
1867         There is no need to normalize those vectors since we are only interested in
 
1868         the sign of the cross product and not in the product value.
 
1870         NOTE: here we assume the face vertices are in WorldCoordinates, so
 
1871         please transform the object _before_ doing the test.
 
1874         normal = Vector(face.no)
 
1875         camPos = self._getObjPosition(self.cameraObj)
 
1878         # View Vector in orthographics projections is the view Direction of
 
1880         if self.cameraObj.data.getType() == 1:
 
1881             view_vect = self._cameraViewVector()
 
1883         # View vector in perspective projections can be considered as
 
1884         # the difference between the camera position and one point of
 
1885         # the face, we choose the farthest point from the camera.
 
1886         if self.cameraObj.data.getType() == 0:
 
1887             vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
 
1891         # if d > 0 the face is visible from the camera
 
1892         d = view_vect * normal
 
1902     def _doSceneClipping(self, scene):
 
1903         """Clip whole objects against the View Frustum.
 
1905         For now clip away only objects according to their center position.
 
1908         cpos = self._getObjPosition(self.cameraObj)
 
1909         view_vect = self._cameraViewVector()
 
1911         near = self.cameraObj.data.clipStart
 
1912         far  = self.cameraObj.data.clipEnd
 
1914         aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
 
1915         fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
 
1916         fovy = fovy * 360.0/pi
 
1918         Objects = scene.getChildren()
 
1920             if o.getType() != 'Mesh': continue;
 
1922             obj_vect = Vector(cpos) - self._getObjPosition(o)
 
1924             d = obj_vect*view_vect
 
1925             theta = AngleBetweenVecs(obj_vect, view_vect)
 
1927             # if the object is outside the view frustum, clip it away
 
1928             if (d < near) or (d > far) or (theta > fovy):
 
1931     def _doConvertGeometricObjsToMesh(self, scene):
 
1932         """Convert all "geometric" objects to mesh ones.
 
1934         geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
 
1935         #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
 
1937         Objects = scene.getChildren()
 
1938         objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
 
1941             obj = self._convertToRawMeshObj(obj)
 
1943             scene.unlink(old_obj)
 
1946             # XXX Workaround for Text and Curve which have some normals
 
1947             # inverted when they are converted to Mesh, REMOVE that when
 
1948             # blender will fix that!!
 
1949             if old_obj.getType() in ['Curve', 'Text']:
 
1950                 me = obj.getData(mesh=1)
 
1951                 for f in me.faces: f.sel = 1;
 
1952                 for v in me.verts: v.sel = 1;
 
1959     def _doSceneDepthSorting(self, scene):
 
1960         """Sort objects in the scene.
 
1962         The object sorting is done accordingly to the object centers.
 
1965         c = self._getObjPosition(self.cameraObj)
 
1967         by_center_pos = (lambda o1, o2:
 
1968                 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
 
1969                 cmp((self._getObjPosition(o1) - Vector(c)).length,
 
1970                     (self._getObjPosition(o2) - Vector(c)).length)
 
1973         # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
 
1974         # then ob1 goes farther than obj2, useful when obj2 has holes
 
1977         Objects = scene.getChildren()
 
1978         Objects.sort(by_center_pos)
 
1985     def _joinMeshObjectsInScene(self, scene):
 
1986         """Merge all the Mesh Objects in a scene into a single Mesh Object.
 
1989         oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
 
1991         # FIXME: Object.join() do not work if the list contains 1 object
 
1995         mesh = Mesh.New('BigOne')
 
1996         bigObj = Object.New('Mesh', 'BigOne')
 
2003         except RuntimeError:
 
2004             print "\nWarning! - Can't Join Objects\n"
 
2005             scene.unlink(bigObj)
 
2008             print "Objects Type error?"
 
2016     # Per object/mesh methods
 
2018     def _convertToRawMeshObj(self, object):
 
2019         """Convert geometry based object to a mesh object.
 
2021         me = Mesh.New('RawMesh_'+object.name)
 
2022         me.getFromObject(object.name)
 
2024         newObject = Object.New('Mesh', 'RawMesh_'+object.name)
 
2027         # If the object has no materials set a default material
 
2028         if not me.materials:
 
2029             me.materials = [Material.New()]
 
2030             #for f in me.faces: f.mat = 0
 
2032         newObject.setMatrix(object.getMatrix())
 
2036     def _doModelingTransformation(self, mesh, matrix):
 
2037         """Transform object coordinates to world coordinates.
 
2039         This step is done simply applying to the object its tranformation
 
2040         matrix and recalculating its normals.
 
2042         # XXX FIXME: blender do not transform normals in the right way when
 
2043         # there are negative scale values
 
2044         if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
 
2045             print "WARNING: Negative scales, expect incorrect results!"
 
2047         mesh.transform(matrix, True)
 
2049     def _doBackFaceCulling(self, mesh):
 
2050         """Simple Backface Culling routine.
 
2052         At this level we simply do a visibility test face by face and then
 
2053         select the vertices belonging to visible faces.
 
2056         # Select all vertices, so edges can be displayed even if there are no
 
2058         for v in mesh.verts:
 
2061         Mesh.Mode(Mesh.SelectModes['FACE'])
 
2063         for f in mesh.faces:
 
2065             if self._isFaceVisible(f):
 
2068     def _doLighting(self, mesh):
 
2069         """Apply an Illumination and shading model to the object.
 
2071         The model used is the Phong one, it may be inefficient,
 
2072         but I'm just learning about rendering and starting from Phong seemed
 
2073         the most natural way.
 
2076         # If the mesh has vertex colors already, use them,
 
2077         # otherwise turn them on and do some calculations
 
2078         if mesh.vertexColors:
 
2080         mesh.vertexColors = 1
 
2082         materials = mesh.materials
 
2084         camPos = self._getObjPosition(self.cameraObj)
 
2086         # We do per-face color calculation (FLAT Shading), we can easily turn
 
2087         # to a per-vertex calculation if we want to implement some shading
 
2088         # technique. For an example see:
 
2089         # http://www.miralab.unige.ch/papers/368.pdf
 
2090         for f in mesh.faces:
 
2096                 mat = materials[f.mat]
 
2098             # A new default material
 
2100                 mat = Material.New('defMat')
 
2102             # Check if it is a shadeless material
 
2103             elif mat.getMode() & Material.Modes['SHADELESS']:
 
2105                 # Convert to a value between 0 and 255
 
2106                 tmp_col = [ int(c * 255.0) for c in I]
 
2117             # do vertex color calculation
 
2119             TotDiffSpec = Vector([0.0, 0.0, 0.0])
 
2121             for l in self.lights:
 
2123                 light_pos = self._getObjPosition(l)
 
2124                 light = light_obj.getData()
 
2126                 L = Vector(light_pos).normalize()
 
2128                 V = (Vector(camPos) - Vector(f.cent)).normalize()
 
2130                 N = Vector(f.no).normalize()
 
2132                 if config.polygons['SHADING'] == 'TOON':
 
2133                     NL = ShadingUtils.toonShading(N*L)
 
2137                 # Should we use NL instead of (N*L) here?
 
2138                 R = 2 * (N*L) * N - L
 
2140                 Ip = light.getEnergy()
 
2142                 # Diffuse co-efficient
 
2143                 kd = mat.getRef() * Vector(mat.getRGBCol())
 
2145                     kd[i] *= light.col[i]
 
2147                 Idiff = Ip * kd * max(0, NL)
 
2150                 # Specular component
 
2151                 ks = mat.getSpec() * Vector(mat.getSpecCol())
 
2152                 ns = mat.getHardness()
 
2153                 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
 
2155                 TotDiffSpec += (Idiff+Ispec)
 
2159             Iamb = Vector(Blender.World.Get()[0].getAmb())
 
2162             # Emissive component (convert to a triplet)
 
2163             ki = Vector([mat.getEmit()]*3)
 
2165             #I = ki + Iamb + (Idiff + Ispec)
 
2166             I = ki + (ka * Iamb) + TotDiffSpec
 
2169             # Set Alpha component
 
2171             I.append(mat.getAlpha())
 
2173             # Clamp I values between 0 and 1
 
2174             I = [ min(c, 1) for c in I]
 
2175             I = [ max(0, c) for c in I]
 
2177             # Convert to a value between 0 and 255
 
2178             tmp_col = [ int(c * 255.0) for c in I]
 
2186     def _doProjection(self, mesh, projector):
 
2187         """Apply Viewing and Projection tranformations.
 
2190         for v in mesh.verts:
 
2191             p = projector.doProjection(v.co[:])
 
2196         #mesh.recalcNormals()
 
2199         # We could reeset Camera matrix, since now
 
2200         # we are in Normalized Viewing Coordinates,
 
2201         # but doung that would affect World Coordinate
 
2202         # processing for other objects
 
2204         #self.cameraObj.data.type = 1
 
2205         #self.cameraObj.data.scale = 2.0
 
2206         #m = Matrix().identity()
 
2207         #self.cameraObj.setMatrix(m)
 
2209     def _doViewFrustumClipping(self, mesh):
 
2210         """Clip faces against the View Frustum.
 
2214     def __simpleDepthSort(self, mesh):
 
2215         """Sort faces by the furthest vertex.
 
2217         This simple mesthod is known also as the painter algorithm, and it
 
2218         solves HSR correctly only for convex meshes.
 
2223         # The sorting requires circa n*log(n) steps
 
2225         progress.setActivity("HSR: Painter", n*log(n))
 
2227         by_furthest_z = (lambda f1, f2: progress.update() and
 
2228                 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
 
2231         # FIXME: using NMesh to sort faces. We should avoid that!
 
2232         nmesh = NMesh.GetRaw(mesh.name)
 
2234         # remember that _higher_ z values mean further points
 
2235         nmesh.faces.sort(by_furthest_z)
 
2236         nmesh.faces.reverse()
 
2241     def __newellDepthSort(self, mesh):
 
2242         """Newell's depth sorting.
 
2248         # Find non planar quads and convert them to triangle
 
2249         #for f in mesh.faces:
 
2251         #    if is_nonplanar_quad(f.v):
 
2252         #        print "NON QUAD??"
 
2256         # Now reselect all faces
 
2257         for f in mesh.faces:
 
2259         mesh.quadToTriangle()
 
2261         # FIXME: using NMesh to sort faces. We should avoid that!
 
2262         nmesh = NMesh.GetRaw(mesh.name)
 
2264         # remember that _higher_ z values mean further points
 
2265         nmesh.faces.sort(by_furthest_z)
 
2266         nmesh.faces.reverse()
 
2268         # Begin depth sort tests
 
2270         # use the smooth flag to set marked faces
 
2271         for f in nmesh.faces:
 
2274         facelist = nmesh.faces[:]
 
2278         # The steps are _at_least_ equal to len(facelist), we do not count the
 
2279         # feces coming out from splitting!!
 
2280         progress.setActivity("HSR: Newell", len(facelist))
 
2281         #progress.setQuiet(True)
 
2284         while len(facelist):
 
2285             debug("\n----------------------\n")
 
2286             debug("len(facelits): %d\n" % len(facelist))
 
2289             pSign = sign(P.normal[2])
 
2291             # We can discard faces parallel to the view vector
 
2292             #if P.normal[2] == 0:
 
2293             #    facelist.remove(P)
 
2299             for Q in facelist[1:]:
 
2301                 debug("P.smooth: " + str(P.smooth) + "\n")
 
2302                 debug("Q.smooth: " + str(Q.smooth) + "\n")
 
2305                 qSign = sign(Q.normal[2])
 
2306                 # TODO: check also if Q is parallel??
 
2308                 # Test 0: We need to test only those Qs whose furthest vertex
 
2309                 # is closer to the observer than the closest vertex of P.
 
2311                 zP = [v.co[2] for v in P.v]
 
2312                 zQ = [v.co[2] for v in Q.v]
 
2313                 notZOverlap = min(zP) > max(zQ) + EPS
 
2317                     debug("NOT Z OVERLAP!\n")
 
2319                         # If Q is not marked then we can safely print P
 
2322                         debug("met a marked face\n")
 
2326                 # Test 1: X extent overlapping
 
2327                 xP = [v.co[0] for v in P.v]
 
2328                 xQ = [v.co[0] for v in Q.v]
 
2329                 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
 
2330                 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
 
2334                     debug("NOT X OVERLAP!\n")
 
2338                 # Test 2: Y extent Overlapping
 
2339                 yP = [v.co[1] for v in P.v]
 
2340                 yQ = [v.co[1] for v in Q.v]
 
2341                 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
 
2342                 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
 
2346                     debug("NOT Y OVERLAP!\n")
 
2350                 # Test 3: P vertices are all behind the plane of Q
 
2353                     d = qSign * HSR.Distance(Vector(Pi), Q)
 
2356                 pVerticesBehindPlaneQ = (n == len(P))
 
2358                 if pVerticesBehindPlaneQ:
 
2360                     debug("P BEHIND Q!\n")
 
2364                 # Test 4: Q vertices in front of the plane of P
 
2367                     d = pSign * HSR.Distance(Vector(Qi), P)
 
2370                 qVerticesInFrontPlaneP = (n == len(Q))
 
2372                 if qVerticesInFrontPlaneP:
 
2374                     debug("Q IN FRONT OF P!\n")
 
2378                 # Test 5: Check if projections of polygons effectively overlap,
 
2379                 # in previous tests we checked only bounding boxes.
 
2381                 #if not projectionsOverlap(P, Q):
 
2382                 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
 
2384                     debug("Projections do not overlap!\n")
 
2387                 # We still can't say if P obscures Q.
 
2389                 # But if Q is marked we do a face-split trying to resolve a
 
2390                 # difficulty (maybe a visibility cycle).
 
2393                     debug("Possibly a cycle detected!\n")
 
2394                     debug("Split here!!\n")
 
2396                     facelist = HSR.facesplit(P, Q, facelist, nmesh)
 
2400                 # The question now is: Does Q obscure P?
 
2403                 # Test 3bis: Q vertices are all behind the plane of P
 
2406                     d = pSign * HSR.Distance(Vector(Qi), P)
 
2409                 qVerticesBehindPlaneP = (n == len(Q))
 
2411                 if qVerticesBehindPlaneP:
 
2412                     debug("\nTest 3bis\n")
 
2413                     debug("Q BEHIND P!\n")
 
2416                 # Test 4bis: P vertices in front of the plane of Q
 
2419                     d = qSign * HSR.Distance(Vector(Pi), Q)
 
2422                 pVerticesInFrontPlaneQ = (n == len(P))
 
2424                 if pVerticesInFrontPlaneQ:
 
2425                     debug("\nTest 4bis\n")
 
2426                     debug("P IN FRONT OF Q!\n")
 
2429                 # We don't even know if Q does obscure P, so they should
 
2430                 # intersect each other, split one of them in two parts.
 
2431                 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
 
2432                     debug("\nSimple Intersection?\n")
 
2433                     debug("Test 3bis or 4bis failed\n")
 
2434                     debug("Split here!!2\n")
 
2436                     facelist = HSR.facesplit(P, Q, facelist, nmesh)
 
2441                 facelist.insert(0, Q)
 
2444                 debug("Q marked!\n")
 
2448             if split_done == 0 and face_marked == 0:
 
2451                 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
 
2455             if len(facelist) == 870:
 
2456                 dumpfaces([P, Q], "loopdebug.svg")
 
2459             #if facelist == None:
 
2461             #    print [v.co for v in P]
 
2462             #    print [v.co for v in Q]
 
2465             # end of while len(facelist)
 
2468         nmesh.faces = maplist
 
2469         #for f in nmesh.faces:
 
2475     def _doHiddenSurfaceRemoval(self, mesh):
 
2476         """Do HSR for the given mesh.
 
2478         if len(mesh.faces) == 0:
 
2481         if config.polygons['HSR'] == 'PAINTER':
 
2482             print "\nUsing the Painter algorithm for HSR."
 
2483             self.__simpleDepthSort(mesh)
 
2485         elif config.polygons['HSR'] == 'NEWELL':
 
2486             print "\nUsing the Newell's algorithm for HSR."
 
2487             self.__newellDepthSort(mesh)
 
2490     def _doEdgesStyle(self, mesh, edgestyleSelect):
 
2491         """Process Mesh Edges accroding to a given selection style.
 
2493         Examples of algorithms:
 
2496             given an edge if its adjacent faces have the same normal (that is
 
2497             they are complanar), than deselect it.
 
2500             given an edge if one its adjacent faces is frontfacing and the
 
2501             other is backfacing, than select it, else deselect.
 
2504         Mesh.Mode(Mesh.SelectModes['EDGE'])
 
2506         edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
 
2508         for i,edge_faces in enumerate(edge_cache):
 
2509             mesh.edges[i].sel = 0
 
2510             if edgestyleSelect(edge_faces):
 
2511                 mesh.edges[i].sel = 1
 
2514         for e in mesh.edges:
 
2517             if edgestyleSelect(e, mesh):
 
2522 # ---------------------------------------------------------------------
 
2524 ## GUI Class and Main Program
 
2526 # ---------------------------------------------------------------------
 
2529 from Blender import BGL, Draw
 
2530 from Blender.BGL import *
 
2536         # Output Format menu 
 
2537         output_format = config.output['FORMAT']
 
2538         default_value = outputWriters.keys().index(output_format)+1
 
2539         GUI.outFormatMenu = Draw.Create(default_value)
 
2540         GUI.evtOutFormatMenu = 0
 
2542         # Animation toggle button
 
2543         GUI.animToggle = Draw.Create(config.output['ANIMATION'])
 
2544         GUI.evtAnimToggle = 1
 
2546         # Join Objects toggle button
 
2547         GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
 
2548         GUI.evtJoinObjsToggle = 2
 
2550         # Render filled polygons
 
2551         GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
 
2553         # Shading Style menu 
 
2554         shading_style = config.polygons['SHADING']
 
2555         default_value = shadingStyles.keys().index(shading_style)+1
 
2556         GUI.shadingStyleMenu = Draw.Create(default_value)
 
2557         GUI.evtShadingStyleMenu = 21
 
2559         GUI.evtPolygonsToggle = 3
 
2560         # We hide the config.polygons['EXPANSION_TRICK'], for now
 
2562         # Render polygon edges
 
2563         GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
 
2564         GUI.evtShowEdgesToggle = 4
 
2566         # Render hidden edges
 
2567         GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
 
2568         GUI.evtShowHiddenEdgesToggle = 5
 
2571         edge_style = config.edges['STYLE']
 
2572         default_value = edgeStyles.keys().index(edge_style)+1
 
2573         GUI.edgeStyleMenu = Draw.Create(default_value)
 
2574         GUI.evtEdgeStyleMenu = 6
 
2577         GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
 
2578         GUI.evtEdgeWidthSlider = 7
 
2581         c = config.edges['COLOR']
 
2582         GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
 
2583         GUI.evtEdgeColorPicker = 71
 
2586         GUI.evtRenderButton = 8
 
2589         GUI.evtExitButton = 9
 
2593         # initialize static members
 
2596         glClear(GL_COLOR_BUFFER_BIT)
 
2597         glColor3f(0.0, 0.0, 0.0)
 
2598         glRasterPos2i(10, 350)
 
2599         Draw.Text("VRM: Vector Rendering Method script. Version %s." %
 
2601         glRasterPos2i(10, 335)
 
2602         Draw.Text("Press Q or ESC to quit.")
 
2604         # Build the output format menu
 
2605         glRasterPos2i(10, 310)
 
2606         Draw.Text("Select the output Format:")
 
2607         outMenuStruct = "Output Format %t"
 
2608         for t in outputWriters.keys():
 
2609            outMenuStruct = outMenuStruct + "|%s" % t
 
2610         GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
 
2611                 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
 
2614         GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
 
2615                 10, 260, 160, 18, GUI.animToggle.val,
 
2616                 "Toggle rendering of animations")
 
2618         # Join Objects toggle
 
2619         GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
 
2620                 10, 235, 160, 18, GUI.joinObjsToggle.val,
 
2621                 "Join objects in the rendered file")
 
2624         Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
 
2626         Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
 
2629         glRasterPos2i(200, 310)
 
2630         Draw.Text("Rendering Style:")
 
2633         GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
 
2634                 200, 285, 160, 18, GUI.polygonsToggle.val,
 
2635                 "Render filled polygons")
 
2637         if GUI.polygonsToggle.val == 1:
 
2639             # Polygon Shading Style
 
2640             shadingStyleMenuStruct = "Shading Style %t"
 
2641             for t in shadingStyles.keys():
 
2642                 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
 
2643             GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
 
2644                     200, 260, 160, 18, GUI.shadingStyleMenu.val,
 
2645                     "Choose the shading style")
 
2649         GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
 
2650                 200, 235, 160, 18, GUI.showEdgesToggle.val,
 
2651                 "Render polygon edges")
 
2653         if GUI.showEdgesToggle.val == 1:
 
2656             edgeStyleMenuStruct = "Edge Style %t"
 
2657             for t in edgeStyles.keys():
 
2658                 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
 
2659             GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
 
2660                     200, 210, 160, 18, GUI.edgeStyleMenu.val,
 
2661                     "Choose the edge style")
 
2664             GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
 
2665                     200, 185, 140, 18, GUI.edgeWidthSlider.val,
 
2666                     0.0, 10.0, 0, "Change Edge Width")
 
2669             GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
 
2670                     342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
 
2673             GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
 
2674                     GUI.evtShowHiddenEdgesToggle,
 
2675                     200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
 
2676                     "Render hidden edges as dashed lines")
 
2678         glRasterPos2i(10, 160)
 
2679         Draw.Text("%s (c) 2006" % __author__)
 
2681     def event(evt, val):
 
2683         if evt == Draw.ESCKEY or evt == Draw.QKEY:
 
2690     def button_event(evt):
 
2692         if evt == GUI.evtExitButton:
 
2695         elif evt == GUI.evtOutFormatMenu:
 
2696             i = GUI.outFormatMenu.val - 1
 
2697             config.output['FORMAT']= outputWriters.keys()[i]
 
2699         elif evt == GUI.evtAnimToggle:
 
2700             config.output['ANIMATION'] = bool(GUI.animToggle.val)
 
2702         elif evt == GUI.evtJoinObjsToggle:
 
2703             config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
 
2705         elif evt == GUI.evtPolygonsToggle:
 
2706             config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
 
2708         elif evt == GUI.evtShadingStyleMenu:
 
2709             i = GUI.shadingStyleMenu.val - 1
 
2710             config.polygons['SHADING'] = shadingStyles.keys()[i]
 
2712         elif evt == GUI.evtShowEdgesToggle:
 
2713             config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
 
2715         elif evt == GUI.evtShowHiddenEdgesToggle:
 
2716             config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
 
2718         elif evt == GUI.evtEdgeStyleMenu:
 
2719             i = GUI.edgeStyleMenu.val - 1
 
2720             config.edges['STYLE'] = edgeStyles.keys()[i]
 
2722         elif evt == GUI.evtEdgeWidthSlider:
 
2723             config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
 
2725         elif evt == GUI.evtEdgeColorPicker:
 
2726             config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
 
2728         elif evt == GUI.evtRenderButton:
 
2729             label = "Save %s" % config.output['FORMAT']
 
2730             # Show the File Selector
 
2732             Blender.Window.FileSelector(vectorize, label, outputfile)
 
2735             print "Event: %d not handled!" % evt
 
2742         from pprint import pprint
 
2744         pprint(config.output)
 
2745         pprint(config.polygons)
 
2746         pprint(config.edges)
 
2748     _init = staticmethod(_init)
 
2749     draw = staticmethod(draw)
 
2750     event = staticmethod(event)
 
2751     button_event = staticmethod(button_event)
 
2752     conf_debug = staticmethod(conf_debug)
 
2754 # A wrapper function for the vectorizing process
 
2755 def vectorize(filename):
 
2756     """The vectorizing process is as follows:
 
2758      - Instanciate the writer and the renderer
 
2763         print "\nERROR: invalid file name!"
 
2766     from Blender import Window
 
2767     editmode = Window.EditMode()
 
2768     if editmode: Window.EditMode(0)
 
2770     actualWriter = outputWriters[config.output['FORMAT']]
 
2771     writer = actualWriter(filename)
 
2773     renderer = Renderer()
 
2774     renderer.doRendering(writer, config.output['ANIMATION'])
 
2776     if editmode: Window.EditMode(1) 
 
2781 if __name__ == "__main__":
 
2786     basename = Blender.sys.basename(Blender.Get('filename'))
 
2788         outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
 
2790     if Blender.mode == 'background':
 
2791         progress = ConsoleProgressIndicator()
 
2792         vectorize(outputfile)
 
2794         progress = GraphicalProgressIndicator()
 
2795         Draw.Register(GUI.draw, GUI.event, GUI.button_event)