Process only object that are on visible layers
[vrm.git] / vrm.py
1 #!BPY
2 """
3 Name: 'VRM'
4 Blender: 242
5 Group: 'Render'
6 Tooltip: 'Vector Rendering Method script'
7 """
8
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://projects.blender.org/projects/vrm"]
11 __version__ = "0.3.beta"
12
13 __bpydoc__ = """\
14     Render the scene and save the result in vector format.
15 """
16
17 # ---------------------------------------------------------------------
18 #    Copyright (c) 2006 Antonio Ospite
19 #
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.
24 #
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.
29 #
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
33 #
34 # ---------------------------------------------------------------------
35 #
36 # Additional credits:
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.
41 #
42 # ---------------------------------------------------------------------
43
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 #   - Review how selections are made (this script uses selection states of
48 #     primitives to represent visibility infos)
49 #   - Use a data structure other than Mesh to represent the 2D image? 
50 #     Think to a way to merge (adjacent) polygons that have the same color.
51 #     Or a way to use paths for silhouettes and contours.
52 #   - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
53 #     not support SMIL for animations)
54 #   - Switch to the Mesh structure, should be considerably faster
55 #     (partially done, but with Mesh we cannot sort faces, yet)
56 #   - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
57 #   - Implement Shading Styles? (partially done, to make more flexible).
58 #   - Add Vector Writers other than SVG.
59 #   - set the background color!
60 #   - Check memory use!!
61 #
62 # ---------------------------------------------------------------------
63 #
64 # Changelog:
65 #
66 #   vrm-0.3.py  - ...
67 #     * First release after code restucturing.
68 #       Now the script offers a useful set of functionalities
69 #       and it can render animations, too.
70 #     * Optimization in Renderer.doEdgeStyle(), build a topology cache
71 #       so to speed up the lookup of adjacent faces of an edge.
72 #       Thanks ideasman42.
73 #     * The SVG output is now SVG 1.0 valid.
74 #       Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
75 #     * Progress indicator during HSR.
76 #     * Initial SWF output support (using ming)
77 #     * Fixed a bug in the animation code, now the projection matrix is
78 #       recalculated at each frame!
79 #     * PDF output (using reportlab)
80 #     * Fixed another problem in the animation code the current frame was off
81 #       by one in the case of camera movement.
82 #     * Use fps as specified in blender when VectorWriter handles animation
83 #     * Remove the real file opening in the abstract VectorWriter
84 #     * View frustum clipping
85 #     * Scene clipping done using bounding box instead of object center
86 #     * Fix camera type selection for blender>2.43 (Thanks to Thomas Lachmann)
87 #     * Compatibility with python 2.3
88 #     * Process only object that are on visible layers.
89 #
90 # ---------------------------------------------------------------------
91
92 import Blender
93 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
94 from Blender.Mathutils import *
95 from math import *
96 import sys, time
97
98 def uniq(alist):
99     tmpdict = dict()
100     return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
101     # in python > 2.4 we ca use the following
102     #return [ u for u in alist if u not in locals()['_[1]'] ]
103
104
105 # Constants
106 EPS = 10e-5
107
108 # We use a global progress Indicator Object
109 progress = None
110
111
112 # Some global settings
113
114 class config:
115     polygons = dict()
116     polygons['SHOW'] = True
117     polygons['SHADING'] = 'FLAT' # FLAT or TOON
118     polygons['HSR'] = 'NEWELL' # PAINTER or NEWELL
119     # Hidden to the user for now
120     polygons['EXPANSION_TRICK'] = True
121
122     polygons['TOON_LEVELS'] = 2
123
124     edges = dict()
125     edges['SHOW'] = False
126     edges['SHOW_HIDDEN'] = False
127     edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
128     edges['WIDTH'] = 2
129     edges['COLOR'] = [0, 0, 0]
130
131     output = dict()
132     output['FORMAT'] = 'SVG'
133     output['ANIMATION'] = False
134     output['JOIN_OBJECTS'] = True
135
136
137 # Utility functions
138 print_debug = False
139
140 def dumpfaces(flist, filename):
141     """Dump a single face to a file.
142     """
143     if not print_debug:
144         return
145
146     class tmpmesh:
147         pass
148
149     m = tmpmesh()
150     m.faces = flist
151
152     writerobj = SVGVectorWriter(filename)
153
154     writerobj.open()
155     writerobj._printPolygons(m)
156
157     writerobj.close()
158
159 def debug(msg):
160     if print_debug:
161         sys.stderr.write(msg)
162
163 def EQ(v1, v2):
164     return (abs(v1[0]-v2[0]) < EPS and 
165             abs(v1[1]-v2[1]) < EPS )
166 by_furthest_z = (lambda f1, f2:
167     cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
168     )
169
170 def sign(x):
171
172     if x < -EPS:
173     #if x < 0:
174         return -1
175     elif x > EPS:
176     #elif x > 0:
177         return 1
178     else:
179         return 0
180
181
182 # ---------------------------------------------------------------------
183 #
184 ## HSR Utility class
185 #
186 # ---------------------------------------------------------------------
187
188 EPS = 10e-5
189 INF = 10e5
190
191 class HSR:
192     """A utility class for HSR processing.
193     """
194
195     def is_nonplanar_quad(face):
196         """Determine if a quad is non-planar.
197
198         From: http://mathworld.wolfram.com/Coplanar.html
199
200         Geometric objects lying in a common plane are said to be coplanar.
201         Three noncollinear points determine a plane and so are trivially coplanar.
202         Four points are coplanar iff the volume of the tetrahedron defined by them is
203         0, 
204         
205             | x_1 y_1 z_1 1 |
206             | x_2 y_2 z_2 1 |
207             | x_3 y_3 z_3 1 |
208             | x_4 y_4 z_4 1 | == 0
209
210         Coplanarity is equivalent to the statement that the pair of lines
211         determined by the four points are not skew, and can be equivalently stated
212         in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
213
214         An arbitrary number of n points x_1, ..., x_n can be tested for
215         coplanarity by finding the point-plane distances of the points
216         x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
217         and checking if they are all zero.
218         If so, the points are all coplanar.
219
220         We here check only for 4-point complanarity.
221         """
222         n = len(face)
223
224         # assert(n>4)
225         if n < 3 or n > 4:
226             print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
227             raise AssertionError
228
229         elif n == 3:
230             # three points must be complanar
231             return False
232         else: # n == 4
233             x1 = Vector(face[0].co)
234             x2 = Vector(face[1].co)
235             x3 = Vector(face[2].co)
236             x4 = Vector(face[3].co)
237
238             v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
239             if v != 0:
240                 return True
241
242         return False
243
244     is_nonplanar_quad = staticmethod(is_nonplanar_quad)
245
246     def pointInPolygon(poly, v):
247         return False
248
249     pointInPolygon = staticmethod(pointInPolygon)
250
251     def edgeIntersection(s1, s2, do_perturbate=False):
252
253         (x1, y1) = s1[0].co[0], s1[0].co[1]
254         (x2, y2) = s1[1].co[0], s1[1].co[1]
255
256         (x3, y3) = s2[0].co[0], s2[0].co[1]
257         (x4, y4) = s2[1].co[0], s2[1].co[1]
258
259         #z1 = s1[0].co[2]
260         #z2 = s1[1].co[2]
261         #z3 = s2[0].co[2]
262         #z4 = s2[1].co[2]
263
264
265         # calculate delta values (vector components)
266         dx1 = x2 - x1;
267         dx2 = x4 - x3;
268         dy1 = y2 - y1;
269         dy2 = y4 - y3;
270
271         #dz1 = z2 - z1;
272         #dz2 = z4 - z3;
273
274         C = dy2 * dx1 - dx2 * dy1 #  /* cross product */
275         if C == 0:  #/* parallel */
276             return None
277
278         dx3 = x1 - x3 # /* combined origin offset vector */
279         dy3 = y1 - y3
280
281         a1 = (dy3 * dx2 - dx3 * dy2) / C;
282         a2 = (dy3 * dx1 - dx3 * dy1) / C;
283
284         # check for degeneracies
285         #print_debug("\n")
286         #print_debug(str(a1)+"\n")
287         #print_debug(str(a2)+"\n\n")
288
289         if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
290             # Intersection on boundaries, we consider the point external?
291             return None
292
293         elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): #  /* lines cross */
294             x = x1 + a1*dx1
295             y = y1 + a1*dy1
296
297             #z = z1 + a1*dz1
298             z = 0
299             return (NMesh.Vert(x, y, z), a1, a2)
300
301         else:
302             # lines have intersections but not those segments
303             return None
304
305     edgeIntersection = staticmethod(edgeIntersection)
306
307     def isVertInside(self, v):
308         winding_number = 0
309         coincidence = False
310
311         # Create point at infinity
312         point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
313
314         for i in range(len(self.v)):
315             s1 = (point_at_infinity, v)
316             s2 = (self.v[i-1], self.v[i])
317
318             if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
319                 coincidence = True
320
321             if HSR.edgeIntersection(s1, s2, do_perturbate=False):
322                 winding_number += 1
323
324         # Check even or odd
325         if winding_number % 2 == 0 :
326             return False
327         else:
328             if coincidence:
329                 return False
330             return True
331
332     isVertInside = staticmethod(isVertInside)
333
334
335     def det(a, b, c):
336         return ((b[0] - a[0]) * (c[1] - a[1]) -
337                 (b[1] - a[1]) * (c[0] - a[0]) )
338     
339     det = staticmethod(det)
340
341     def pointInPolygon(q, P):
342         is_in = False
343
344         point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
345
346         det = HSR.det
347
348         for i in range(len(P.v)):
349             p0 = P.v[i-1]
350             p1 = P.v[i]
351             if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
352                 if det(p0.co, p1.co, q.co) == 0 :
353                     #print "On Boundary"
354                     return False
355                 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
356                     is_in = not is_in
357
358         return is_in
359
360     pointInPolygon = staticmethod(pointInPolygon)
361
362     def projectionsOverlap(f1, f2):
363         """ If you have nonconvex, but still simple polygons, an acceptable method
364         is to iterate over all vertices and perform the Point-in-polygon test[1].
365         The advantage of this method is that you can compute the exact
366         intersection point and collision normal that you will need to simulate
367         collision. When you have the point that lies inside the other polygon, you
368         just iterate over all edges of the second polygon again and look for edge
369         intersections. Note that this method detects collsion when it already
370         happens. This algorithm is fast enough to perform it hundreds of times per
371         sec.  """
372
373         for i in range(len(f1.v)):
374
375
376             # If a point of f1 in inside f2, there is an overlap!
377             v1 = f1.v[i]
378             #if HSR.isVertInside(f2, v1):
379             if HSR.pointInPolygon(v1, f2):
380                 return True
381
382             # If not the polygon can be ovelap as well, so we check for
383             # intersection between an edge of f1 and all the edges of f2
384
385             v0 = f1.v[i-1]
386
387             for j in range(len(f2.v)):
388                 v2 = f2.v[j-1]
389                 v3 = f2.v[j]
390
391                 e1 = v0, v1
392                 e2 = v2, v3
393
394                 intrs = HSR.edgeIntersection(e1, e2)
395                 if intrs:
396                     #print_debug(str(v0.co) + " " + str(v1.co) + " " +
397                     #        str(v2.co) + " " + str(v3.co) )
398                     #print_debug("\nIntersection\n")
399
400                     return True
401
402         return False
403
404     projectionsOverlap = staticmethod(projectionsOverlap)
405
406     def midpoint(p1, p2):
407         """Return the midpoint of two vertices.
408         """
409         m = MidpointVecs(Vector(p1), Vector(p2))
410         mv = NMesh.Vert(m[0], m[1], m[2])
411
412         return mv
413
414     midpoint = staticmethod(midpoint)
415
416     def facesplit(P, Q, facelist, nmesh):
417         """Split P or Q according to the strategy illustrated in the Newell's
418         paper.
419         """
420
421         by_furthest_z = (lambda f1, f2:
422                 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
423                 )
424
425         # Choose if split P on Q plane or vice-versa
426
427         n = 0
428         for Pi in P:
429             d = HSR.Distance(Vector(Pi), Q)
430             if d <= EPS:
431                 n += 1
432         pIntersectQ = (n != len(P))
433
434         n = 0
435         for Qi in Q:
436             d = HSR.Distance(Vector(Qi), P)
437             if d >= -EPS:
438                 n += 1
439         qIntersectP = (n != len(Q))
440
441         newfaces = []
442
443         # 1. If parts of P lie in both half-spaces of Q
444         # then splice P in two with the plane of Q
445         if pIntersectQ:
446             #print "We split P"
447             f = P
448             plane = Q
449
450             newfaces = HSR.splitOn(plane, f)
451
452         # 2. Else if parts of Q lie in both half-space of P
453         # then splice Q in two with the plane of P
454         if qIntersectP and newfaces == None:
455             #print "We split Q"
456             f = Q
457             plane = P
458
459             newfaces = HSR.splitOn(plane, f)
460             #print "After"
461
462         # 3. Else slice P in half through the mid-point of
463         # the longest pair of opposite sides
464         if newfaces == None:
465
466             print "We ignore P..."
467             facelist.remove(P)
468             return facelist
469
470             #f = P
471
472             #if len(P)==3:
473             #    v1 = midpoint(f[0], f[1])
474             #    v2 = midpoint(f[1], f[2])
475             #if len(P)==4:
476             #    v1 = midpoint(f[0], f[1])
477             #    v2 = midpoint(f[2], f[3])
478             #vec3 = (Vector(v2)+10*Vector(f.normal))
479             #
480             #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
481
482             #plane = NMesh.Face([v1, v2, v3])
483             #
484             #newfaces = splitOn(plane, f)
485
486         
487         if newfaces == None:
488             print "Big FAT problem, we weren't able to split POLYGONS!"
489             raise AssertionError
490
491         #print newfaces
492         if newfaces:
493             #for v in f:
494             #    if v not in plane and v in nmesh.verts:
495             #        nmesh.verts.remove(v)
496             for nf in newfaces:
497
498                 nf.mat = f.mat
499                 nf.sel = f.sel
500                 nf.col = [f.col[0]] * len(nf.v)
501
502                 nf.smooth = 0
503
504                 for v in nf:
505                     nmesh.verts.append(v)
506                 # insert pieces in the list
507                 facelist.append(nf)
508
509             facelist.remove(f)
510
511         # and resort the faces
512         facelist.sort(by_furthest_z)
513         facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
514         facelist.reverse()
515
516         #print [ f.smooth for f in facelist ]
517
518         return facelist
519
520     facesplit = staticmethod(facesplit)
521
522     def isOnSegment(v1, v2, p, extremes_internal=False):
523         """Check if point p is in segment v1v2.
524         """
525
526         l1 = (v1-p).length
527         l2 = (v2-p).length
528
529         # Should we consider extreme points as internal ?
530         # The test:
531         # if p == v1 or p == v2:
532         if l1 < EPS or l2 < EPS:
533             return extremes_internal
534
535         l = (v1-v2).length
536
537         # if the sum of l1 and l2 is circa l, then the point is on segment,
538         if abs(l - (l1+l2)) < EPS:
539             return True
540         else:
541             return False
542
543     isOnSegment = staticmethod(isOnSegment)
544
545     def Distance(point, face):
546         """ Calculate the distance between a point and a face.
547
548         An alternative but more expensive method can be:
549
550             ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
551                     Vector(face.no), Vector(point), 0)
552
553             d = Vector(ip - point).length
554
555         See: http://mathworld.wolfram.com/Point-PlaneDistance.html
556         """
557
558         p = Vector(point)
559         plNormal = Vector(face.no)
560         plVert0 = Vector(face.v[0])
561
562         d = (plVert0 * plNormal) - (p * plNormal)
563
564         #d = plNormal * (plVert0 - p)
565
566         #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
567
568         return d
569
570     Distance = staticmethod(Distance)
571
572     def makeFaces(vl):
573         #
574         # make one or two new faces based on a list of vertex-indices
575         #
576         newfaces = []
577
578         if len(vl) <= 4:
579             nf = NMesh.Face()
580
581             for v in vl:
582                 nf.v.append(v)
583
584             newfaces.append(nf)
585
586         else:
587             nf = NMesh.Face()
588
589             nf.v.append(vl[0])
590             nf.v.append(vl[1])
591             nf.v.append(vl[2])
592             nf.v.append(vl[3])
593             newfaces.append(nf)
594
595             nf = NMesh.Face()
596             nf.v.append(vl[3])
597             nf.v.append(vl[4])
598             nf.v.append(vl[0])
599             newfaces.append(nf)
600
601         return newfaces
602
603     makeFaces = staticmethod(makeFaces)
604
605     def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
606         """Split P using the plane of Q.
607         Logic taken from the knife.py python script
608         """
609
610         # Check if P and Q are parallel
611         u = CrossVecs(Vector(Q.no),Vector(P.no))
612         ax = abs(u[0])
613         ay = abs(u[1])
614         az = abs(u[2])
615
616         if (ax+ay+az) < EPS:
617             print "PARALLEL planes!!"
618             return
619
620
621         # The final aim is to find the intersection line between P
622         # and the plane of Q, and split P along this line
623
624         nP = len(P.v)
625
626         # Calculate point-plane Distance between vertices of P and plane Q
627         d = []
628         for i in range(0, nP):
629             d.append(HSR.Distance(P.v[i], Q))
630
631         newVertList = []
632
633         posVertList = []
634         negVertList = []
635         for i in range(nP):
636             d0 = d[i-1]
637             V0 = P.v[i-1]
638
639             d1 = d[i]
640             V1 = P.v[i]
641
642             #print "d0:", d0, "d1:", d1
643
644             # if the vertex lies in the cutplane                        
645             if abs(d1) < EPS:
646                 #print "d1 On cutplane"
647                 posVertList.append(V1)
648                 negVertList.append(V1)
649             else:
650                 # if the previous vertex lies in cutplane
651                 if abs(d0) < EPS:
652                     #print "d0 on Cutplane"
653                     if d1 > 0:
654                         #print "d1 on positive Halfspace"
655                         posVertList.append(V1)
656                     else:
657                         #print "d1 on negative Halfspace"
658                         negVertList.append(V1)
659                 else:
660                     # if they are on the same side of the plane
661                     if d1*d0 > 0:
662                         #print "On the same half-space"
663                         if d1 > 0:
664                             #print "d1 on positive Halfspace"
665                             posVertList.append(V1)
666                         else:
667                             #print "d1 on negative Halfspace"
668                             negVertList.append(V1)
669
670                     # the vertices are not on the same side of the plane, so we have an intersection
671                     else:
672                         #print "Intersection"
673
674                         e = Vector(V0), Vector(V1)
675                         tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
676
677                         inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
678                         if inters == None:
679                             print "Split Break"
680                             break
681
682                         #print "Intersection", inters
683
684                         nv = NMesh.Vert(inters[0], inters[1], inters[2])
685                         newVertList.append(nv)
686
687                         posVertList.append(nv)
688                         negVertList.append(nv)
689
690                         if d1 > 0:
691                             posVertList.append(V1)
692                         else:
693                             negVertList.append(V1)
694
695         
696         # uniq for python > 2.4
697         #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
698         #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
699
700         # a more portable way
701         posVertList = uniq(posVertList)
702         negVertList = uniq(negVertList)
703
704
705         # If vertex are all on the same half-space, return
706         #if len(posVertList) < 3:
707         #    print "Problem, we created a face with less that 3 vertices??"
708         #    posVertList = []
709         #if len(negVertList) < 3:
710         #    print "Problem, we created a face with less that 3 vertices??"
711         #    negVertList = []
712
713         if len(posVertList) < 3 or len(negVertList) < 3:
714             #print "RETURN NONE, SURE???"
715             return None
716
717         if not return_positive_faces:
718             posVertList = []
719         if not return_negative_faces:
720             negVertList = []
721
722         newfaces = HSR.addNewFaces(posVertList, negVertList)
723
724         return newfaces
725
726     splitOn = staticmethod(splitOn)
727
728     def addNewFaces(posVertList, negVertList):
729         # Create new faces resulting from the split
730         outfaces = []
731         if len(posVertList) or len(negVertList):
732
733             #newfaces = [posVertList] + [negVertList]
734             newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
735                     [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
736
737             for nf in newfaces:
738                 if nf and len(nf)>2:
739                     outfaces += HSR.makeFaces(nf)
740
741         return outfaces
742
743
744     addNewFaces = staticmethod(addNewFaces)
745
746
747 # ---------------------------------------------------------------------
748 #
749 ## Mesh Utility class
750 #
751 # ---------------------------------------------------------------------
752
753 class MeshUtils:
754
755     def buildEdgeFaceUsersCache(me):
756         ''' 
757         Takes a mesh and returns a list aligned with the meshes edges.
758         Each item is a list of the faces that use the edge
759         would be the equiv for having ed.face_users as a property
760
761         Taken from .blender/scripts/bpymodules/BPyMesh.py,
762         thanks to ideasman_42.
763         '''
764
765         def sorted_edge_indicies(ed):
766             i1= ed.v1.index
767             i2= ed.v2.index
768             if i1>i2:
769                 i1,i2= i2,i1
770             return i1, i2
771
772         
773         face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
774         for f in me.faces:
775             fvi= [v.index for v in f.v]# face vert idx's
776             for i in xrange(len(f)):
777                 i1= fvi[i]
778                 i2= fvi[i-1]
779                 
780                 if i1>i2:
781                     i1,i2= i2,i1
782                 
783                 face_edges_dict[i1,i2][1].append(f)
784         
785         face_edges= [None] * len(me.edges)
786         for ed_index, ed_faces in face_edges_dict.itervalues():
787             face_edges[ed_index]= ed_faces
788         
789         return face_edges
790
791     def isMeshEdge(adjacent_faces):
792         """Mesh edge rule.
793
794         A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
795         Note: if the edge has no adjacent faces we want to show it as well,
796         useful for "edge only" portion of objects.
797         """
798
799         if len(adjacent_faces) == 0:
800             return True
801
802         selected_faces = [f for f in adjacent_faces if f.sel]
803
804         if len(selected_faces) != 0:
805             return True
806         else:
807             return False
808
809     def isSilhouetteEdge(adjacent_faces):
810         """Silhuette selection rule.
811
812         An edge is a silhuette edge if it is shared by two faces with
813         different selection status or if it is a boundary edge of a selected
814         face.
815         """
816
817         if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
818             (len(adjacent_faces) == 2 and
819                 adjacent_faces[0].sel != adjacent_faces[1].sel)
820             ):
821             return True
822         else:
823             return False
824
825     buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
826     isMeshEdge = staticmethod(isMeshEdge)
827     isSilhouetteEdge = staticmethod(isSilhouetteEdge)
828
829
830 # ---------------------------------------------------------------------
831 #
832 ## Shading Utility class
833 #
834 # ---------------------------------------------------------------------
835
836 class ShadingUtils:
837
838     shademap = None
839
840     def toonShadingMapSetup():
841         levels = config.polygons['TOON_LEVELS']
842
843         texels = 2*levels - 1
844         tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
845
846         return tmp_shademap
847
848     def toonShading(u):
849
850         shademap = ShadingUtils.shademap
851
852         if not shademap:
853             shademap = ShadingUtils.toonShadingMapSetup()
854
855         v = 1.0
856         for i in xrange(0, len(shademap)-1):
857             pivot = (shademap[i]+shademap[i+1])/2.0
858             j = int(u>pivot)
859
860             v = shademap[i+j]
861
862             if v < shademap[i+1]:
863                 return v
864
865         return v
866
867     toonShadingMapSetup = staticmethod(toonShadingMapSetup)
868     toonShading = staticmethod(toonShading)
869
870
871 # ---------------------------------------------------------------------
872 #
873 ## Projections classes
874 #
875 # ---------------------------------------------------------------------
876
877 class Projector:
878     """Calculate the projection of an object given the camera.
879     
880     A projector is useful to so some per-object transformation to obtain the
881     projection of an object given the camera.
882     
883     The main method is #doProjection# see the method description for the
884     parameter list.
885     """
886
887     def __init__(self, cameraObj, canvasRatio):
888         """Calculate the projection matrix.
889
890         The projection matrix depends, in this case, on the camera settings.
891         TAKE CARE: This projector expects vertices in World Coordinates!
892         """
893
894         camera = cameraObj.getData()
895
896         aspect = float(canvasRatio[0])/float(canvasRatio[1])
897         near = camera.clipStart
898         far = camera.clipEnd
899
900         scale = float(camera.scale)
901
902         fovy = atan(0.5/aspect/(camera.lens/32))
903         fovy = fovy * 360.0/pi
904
905
906         if Blender.Get('version') < 243:
907             camPersp = 0
908             camOrtho = 1
909         else:
910             camPersp = 'persp'
911             camOrtho = 'ortho'
912             
913         # What projection do we want?
914         if camera.type == camPersp:
915             mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) 
916         elif camera.type == camOrtho:
917             mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
918         
919
920         # View transformation
921         cam = Matrix(cameraObj.getInverseMatrix())
922         cam.transpose() 
923         
924         mP = mP * cam
925
926         self.projectionMatrix = mP
927
928     ##
929     # Public methods
930     #
931
932     def doProjection(self, v):
933         """Project the point on the view plane.
934
935         Given a vertex calculate the projection using the current projection
936         matrix.
937         """
938         
939         # Note that we have to work on the vertex using homogeneous coordinates
940         # From blender 2.42+ we don't need to resize the vector to be 4d
941         # when applying a 4x4 matrix, but we do that anyway since we need the
942         # 4th coordinate later
943         p = self.projectionMatrix * Vector(v).resize4D()
944         
945         # Perspective division
946         if p[3] != 0:
947             p[0] = p[0]/p[3]
948             p[1] = p[1]/p[3]
949             p[2] = p[2]/p[3]
950
951         # restore the size
952         p[3] = 1.0
953         p.resize3D()
954
955         return p
956
957
958     ##
959     # Private methods
960     #
961     
962     def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
963         """Return a perspective projection matrix.
964         """
965         
966         top = near * tan(fovy * pi / 360.0)
967         bottom = -top
968         left = bottom*aspect
969         right= top*aspect
970         x = (2.0 * near) / (right-left)
971         y = (2.0 * near) / (top-bottom)
972         a = (right+left) / (right-left)
973         b = (top+bottom) / (top - bottom)
974         c = - ((far+near) / (far-near))
975         d = - ((2*far*near)/(far-near))
976         
977         m = Matrix(
978                 [x,   0.0,    a,    0.0],
979                 [0.0,   y,    b,    0.0],
980                 [0.0, 0.0,    c,      d],
981                 [0.0, 0.0, -1.0,    0.0])
982
983         return m
984
985     def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
986         """Return an orthogonal projection matrix.
987         """
988         
989         # The 11 in the formula was found emiprically
990         top = near * tan(fovy * pi / 360.0) * (scale * 11)
991         bottom = -top 
992         left = bottom * aspect
993         right= top * aspect
994         rl = right-left
995         tb = top-bottom
996         fn = near-far 
997         tx = -((right+left)/rl)
998         ty = -((top+bottom)/tb)
999         tz = ((far+near)/fn)
1000
1001         m = Matrix(
1002                 [2.0/rl, 0.0,    0.0,     tx],
1003                 [0.0,    2.0/tb, 0.0,     ty],
1004                 [0.0,    0.0,    2.0/fn,  tz],
1005                 [0.0,    0.0,    0.0,    1.0])
1006         
1007         return m
1008
1009
1010 # ---------------------------------------------------------------------
1011 #
1012 ## Progress Indicator
1013 #
1014 # ---------------------------------------------------------------------
1015
1016 class Progress:
1017     """A model for a progress indicator.
1018     
1019     Do the progress calculation calculation and
1020     the view independent stuff of a progress indicator.
1021     """
1022     def __init__(self, steps=0):
1023         self.name = ""
1024         self.steps = steps
1025         self.completed = 0
1026         self.progress = 0
1027
1028     def setSteps(self, steps):
1029         """Set the number of steps of the activity wich we want to track.
1030         """
1031         self.steps = steps
1032
1033     def getSteps(self):
1034         return self.steps
1035
1036     def setName(self, name):
1037         """Set the name of the activity wich we want to track.
1038         """
1039         self.name = name
1040
1041     def getName(self):
1042         return self.name
1043
1044     def getProgress(self):
1045         return self.progress
1046
1047     def reset(self):
1048         self.completed = 0
1049         self.progress = 0
1050
1051     def update(self):
1052         """Update the model, call this method when one step is completed.
1053         """
1054         if self.progress == 100:
1055             return False
1056
1057         self.completed += 1
1058         self.progress = ( float(self.completed) / float(self.steps) ) * 100
1059         self.progress = int(self.progress)
1060
1061         return True
1062
1063
1064 class ProgressIndicator:
1065     """An abstraction of a View for the Progress Model
1066     """
1067     def __init__(self):
1068
1069         # Use a refresh rate so we do not show the progress at
1070         # every update, but every 'self.refresh_rate' times.
1071         self.refresh_rate = 10
1072         self.shows_counter = 0
1073
1074         self.quiet = False
1075
1076         self.progressModel = None
1077
1078     def setQuiet(self, value):
1079         self.quiet = value
1080
1081     def setActivity(self, name, steps):
1082         """Initialize the Model.
1083
1084         In a future version (with subactivities-progress support) this method
1085         could only set the current activity.
1086         """
1087         self.progressModel = Progress()
1088         self.progressModel.setName(name)
1089         self.progressModel.setSteps(steps)
1090
1091     def getActivity(self):
1092         return self.progressModel
1093
1094     def update(self):
1095         """Update the model and show the actual progress.
1096         """
1097         assert(self.progressModel)
1098
1099         if self.progressModel.update():
1100             if self.quiet:
1101                 return
1102
1103             self.show(self.progressModel.getProgress(),
1104                     self.progressModel.getName())
1105
1106         # We return always True here so we can call the update() method also
1107         # from lambda funcs (putting the call in logical AND with other ops)
1108         return True
1109
1110     def show(self, progress, name=""):
1111         self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1112         if self.shows_counter != 0:
1113             return
1114
1115         if progress == 100:
1116             self.shows_counter = -1
1117
1118
1119 class ConsoleProgressIndicator(ProgressIndicator):
1120     """Show a progress bar on stderr, a la wget.
1121     """
1122     def __init__(self):
1123         ProgressIndicator.__init__(self)
1124
1125         self.swirl_chars = ["-", "\\", "|", "/"]
1126         self.swirl_count = -1
1127
1128     def show(self, progress, name):
1129         ProgressIndicator.show(self, progress, name)
1130         
1131         bar_length = 70
1132         bar_progress = int( (progress/100.0) * bar_length )
1133         bar = ("=" * bar_progress).ljust(bar_length)
1134
1135         self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1136         swirl_char = self.swirl_chars[self.swirl_count]
1137
1138         progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1139
1140         sys.stderr.write(progress_bar+"\r")
1141         if progress == 100:
1142             sys.stderr.write("\n")
1143
1144
1145 class GraphicalProgressIndicator(ProgressIndicator):
1146     """Interface to the Blender.Window.DrawProgressBar() method.
1147     """
1148     def __init__(self):
1149         ProgressIndicator.__init__(self)
1150
1151         #self.swirl_chars = ["-", "\\", "|", "/"]
1152         # We have to use letters with the same width, for now!
1153         # Blender progress bar considers the font widths when
1154         # calculating the progress bar width.
1155         self.swirl_chars = ["\\", "/"]
1156         self.swirl_count = -1
1157
1158     def show(self, progress, name):
1159         ProgressIndicator.show(self, progress)
1160
1161         self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1162         swirl_char = self.swirl_chars[self.swirl_count]
1163
1164         progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1165
1166         # Finally draw  the Progress Bar
1167         Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1168         Window.DrawProgressBar(progress/100.0, progress_text)
1169
1170         if progress == 100:
1171             Window.DrawProgressBar(1, progress_text)
1172             Window.WaitCursor(0)
1173
1174
1175
1176 # ---------------------------------------------------------------------
1177 #
1178 ## 2D Object representation class
1179 #
1180 # ---------------------------------------------------------------------
1181
1182 # TODO: a class to represent the needed properties of a 2D vector image
1183 # For now just using a [N]Mesh structure.
1184
1185
1186 # ---------------------------------------------------------------------
1187 #
1188 ## Vector Drawing Classes
1189 #
1190 # ---------------------------------------------------------------------
1191
1192 ## A generic Writer
1193
1194 class VectorWriter:
1195     """
1196     A class for printing output in a vectorial format.
1197
1198     Given a 2D representation of the 3D scene the class is responsible to
1199     write it is a vector format.
1200
1201     Every subclasses of VectorWriter must have at last the following public
1202     methods:
1203         - open(self)
1204         - close(self)
1205         - printCanvas(self, scene,
1206             doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1207     """
1208     
1209     def __init__(self, fileName):
1210         """Set the output file name and other properties"""
1211
1212         self.outputFileName = fileName
1213         
1214         context = Scene.GetCurrent().getRenderingContext()
1215         self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1216
1217         self.fps = context.fps
1218
1219         self.startFrame = 1
1220         self.endFrame = 1
1221         self.animation = False
1222
1223
1224     ##
1225     # Public Methods
1226     #
1227     
1228     def open(self, startFrame=1, endFrame=1):
1229         if startFrame != endFrame:
1230             self.startFrame = startFrame
1231             self.endFrame = endFrame
1232             self.animation = True
1233
1234         print "Outputting to: ", self.outputFileName
1235
1236         return
1237
1238     def close(self):
1239         return
1240
1241     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1242             showHiddenEdges=False):
1243         """This is the interface for the needed printing routine.
1244         """
1245         return
1246         
1247
1248 ## SVG Writer
1249
1250 class SVGVectorWriter(VectorWriter):
1251     """A concrete class for writing SVG output.
1252     """
1253
1254     def __init__(self, fileName):
1255         """Simply call the parent Contructor.
1256         """
1257         VectorWriter.__init__(self, fileName)
1258
1259         self.file = None
1260
1261
1262     ##
1263     # Public Methods
1264     #
1265
1266     def open(self, startFrame=1, endFrame=1):
1267         """Do some initialization operations.
1268         """
1269         VectorWriter.open(self, startFrame, endFrame)
1270
1271         self.file = open(self.outputFileName, "w")
1272
1273         self._printHeader()
1274
1275     def close(self):
1276         """Do some finalization operation.
1277         """
1278         self._printFooter()
1279
1280         if self.file:
1281             self.file.close()
1282
1283         # remember to call the close method of the parent as last
1284         VectorWriter.close(self)
1285
1286         
1287     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1288             showHiddenEdges=False):
1289         """Convert the scene representation to SVG.
1290         """
1291
1292         Objects = scene.getChildren()
1293
1294         context = scene.getRenderingContext()
1295         framenumber = context.currentFrame()
1296
1297         if self.animation:
1298             framestyle = "display:none"
1299         else:
1300             framestyle = "display:block"
1301         
1302         # Assign an id to this group so we can set properties on it using DOM
1303         self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1304                 (framenumber, framestyle) )
1305
1306
1307         for obj in Objects:
1308
1309             if(obj.getType() != 'Mesh'):
1310                 continue
1311
1312             self.file.write("<g id=\"%s\">\n" % obj.getName())
1313
1314             mesh = obj.getData(mesh=1)
1315
1316             if doPrintPolygons:
1317                 self._printPolygons(mesh)
1318
1319             if doPrintEdges:
1320                 self._printEdges(mesh, showHiddenEdges)
1321             
1322             self.file.write("</g>\n")
1323
1324         self.file.write("</g>\n")
1325
1326     
1327     ##  
1328     # Private Methods
1329     #
1330     
1331     def _calcCanvasCoord(self, v):
1332         """Convert vertex in scene coordinates to canvas coordinates.
1333         """
1334
1335         pt = Vector([0, 0, 0])
1336         
1337         mW = float(self.canvasSize[0])/2.0
1338         mH = float(self.canvasSize[1])/2.0
1339
1340         # rescale to canvas size
1341         pt[0] = v.co[0]*mW + mW
1342         pt[1] = v.co[1]*mH + mH
1343         pt[2] = v.co[2]
1344          
1345         # For now we want (0,0) in the top-left corner of the canvas.
1346         # Mirror and translate along y
1347         pt[1] *= -1
1348         pt[1] += self.canvasSize[1]
1349         
1350         return pt
1351
1352     def _printHeader(self):
1353         """Print SVG header."""
1354
1355         self.file.write("<?xml version=\"1.0\"?>\n")
1356         self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1357         self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1358         self.file.write("<svg version=\"1.0\"\n")
1359         self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1360         self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1361                 self.canvasSize)
1362
1363         if self.animation:
1364             delay = 1000/self.fps
1365
1366             self.file.write("""\n<script type="text/javascript"><![CDATA[
1367             globalStartFrame=%d;
1368             globalEndFrame=%d;
1369
1370             timerID = setInterval("NextFrame()", %d);
1371             globalFrameCounter=%d;
1372             \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1373
1374             self.file.write("""\n
1375             function NextFrame()
1376             {
1377               currentElement  = document.getElementById('frame'+globalFrameCounter)
1378               previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1379
1380               if (!currentElement)
1381               {
1382                 return;
1383               }
1384
1385               if (globalFrameCounter > globalEndFrame)
1386               {
1387                 clearInterval(timerID)
1388               }
1389               else
1390               {
1391                 if(previousElement)
1392                 {
1393                     previousElement.style.display="none";
1394                 }
1395                 currentElement.style.display="block";
1396                 globalFrameCounter++;
1397               }
1398             }
1399             \n]]></script>\n
1400             \n""")
1401                 
1402     def _printFooter(self):
1403         """Print the SVG footer."""
1404
1405         self.file.write("\n</svg>\n")
1406
1407     def _printPolygons(self, mesh): 
1408         """Print the selected (visible) polygons.
1409         """
1410
1411         if len(mesh.faces) == 0:
1412             return
1413
1414         self.file.write("<g>\n")
1415
1416         for face in mesh.faces:
1417             if not face.sel:
1418                continue
1419
1420             self.file.write("<path d=\"")
1421
1422             #p = self._calcCanvasCoord(face.verts[0])
1423             p = self._calcCanvasCoord(face.v[0])
1424             self.file.write("M %g,%g L " % (p[0], p[1]))
1425
1426             for v in face.v[1:]:
1427                 p = self._calcCanvasCoord(v)
1428                 self.file.write("%g,%g " % (p[0], p[1]))
1429             
1430             # get rid of the last blank space, just cosmetics here.
1431             self.file.seek(-1, 1) 
1432             self.file.write(" z\"\n")
1433             
1434             # take as face color the first vertex color
1435             if face.col:
1436                 fcol = face.col[0]
1437                 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1438             else:
1439                 color = [255, 255, 255, 255]
1440
1441             # Convert the color to the #RRGGBB form
1442             str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1443
1444             # Handle transparent polygons
1445             opacity_string = ""
1446             if color[3] != 255:
1447                 opacity = float(color[3])/255.0
1448                 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1449                 #opacity_string = "opacity: %g;" % (opacity)
1450
1451             self.file.write("\tstyle=\"fill:" + str_col + ";")
1452             self.file.write(opacity_string)
1453
1454             # use the stroke property to alleviate the "adjacent edges" problem,
1455             # we simulate polygon expansion using borders,
1456             # see http://www.antigrain.com/svg/index.html for more info
1457             stroke_width = 1.0
1458
1459             # EXPANSION TRICK is not that useful where there is transparency
1460             if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1461                 # str_col = "#000000" # For debug
1462                 self.file.write(" stroke:%s;\n" % str_col)
1463                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1464                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1465
1466             self.file.write("\"/>\n")
1467
1468         self.file.write("</g>\n")
1469
1470     def _printEdges(self, mesh, showHiddenEdges=False):
1471         """Print the wireframe using mesh edges.
1472         """
1473
1474         stroke_width = config.edges['WIDTH']
1475         stroke_col = config.edges['COLOR']
1476         
1477         self.file.write("<g>\n")
1478
1479         for e in mesh.edges:
1480             
1481             hidden_stroke_style = ""
1482             
1483             if e.sel == 0:
1484                 if showHiddenEdges == False:
1485                     continue
1486                 else:
1487                     hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1488
1489             p1 = self._calcCanvasCoord(e.v1)
1490             p2 = self._calcCanvasCoord(e.v2)
1491             
1492             self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1493                     % ( p1[0], p1[1], p2[0], p2[1] ) )
1494             self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1495             self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1496             self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1497             self.file.write(hidden_stroke_style)
1498             self.file.write("\"/>\n")
1499
1500         self.file.write("</g>\n")
1501
1502
1503 ## SWF Writer
1504
1505 try:
1506     from ming import *
1507     SWFSupported = True
1508 except:
1509     SWFSupported = False
1510
1511 class SWFVectorWriter(VectorWriter):
1512     """A concrete class for writing SWF output.
1513     """
1514
1515     def __init__(self, fileName):
1516         """Simply call the parent Contructor.
1517         """
1518         VectorWriter.__init__(self, fileName)
1519
1520         self.movie = None
1521         self.sprite = None
1522
1523
1524     ##
1525     # Public Methods
1526     #
1527
1528     def open(self, startFrame=1, endFrame=1):
1529         """Do some initialization operations.
1530         """
1531         VectorWriter.open(self, startFrame, endFrame)
1532         self.movie = SWFMovie()
1533         self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1534         if self.animation:
1535             self.movie.setRate(self.fps)
1536             numframes = endFrame - startFrame + 1
1537             self.movie.setFrames(numframes)
1538
1539     def close(self):
1540         """Do some finalization operation.
1541         """
1542         self.movie.save(self.outputFileName)
1543
1544         # remember to call the close method of the parent
1545         VectorWriter.close(self)
1546
1547     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1548             showHiddenEdges=False):
1549         """Convert the scene representation to SVG.
1550         """
1551         context = scene.getRenderingContext()
1552         framenumber = context.currentFrame()
1553
1554         Objects = scene.getChildren()
1555
1556         if self.sprite:
1557             self.movie.remove(self.sprite)
1558
1559         sprite = SWFSprite()
1560
1561         for obj in Objects:
1562
1563             if(obj.getType() != 'Mesh'):
1564                 continue
1565
1566             mesh = obj.getData(mesh=1)
1567
1568             if doPrintPolygons:
1569                 self._printPolygons(mesh, sprite)
1570
1571             if doPrintEdges:
1572                 self._printEdges(mesh, sprite, showHiddenEdges)
1573             
1574         sprite.nextFrame()
1575         i = self.movie.add(sprite)
1576         # Remove the instance the next time
1577         self.sprite = i
1578         if self.animation:
1579             self.movie.nextFrame()
1580
1581     
1582     ##  
1583     # Private Methods
1584     #
1585     
1586     def _calcCanvasCoord(self, v):
1587         """Convert vertex in scene coordinates to canvas coordinates.
1588         """
1589
1590         pt = Vector([0, 0, 0])
1591         
1592         mW = float(self.canvasSize[0])/2.0
1593         mH = float(self.canvasSize[1])/2.0
1594
1595         # rescale to canvas size
1596         pt[0] = v.co[0]*mW + mW
1597         pt[1] = v.co[1]*mH + mH
1598         pt[2] = v.co[2]
1599          
1600         # For now we want (0,0) in the top-left corner of the canvas.
1601         # Mirror and translate along y
1602         pt[1] *= -1
1603         pt[1] += self.canvasSize[1]
1604         
1605         return pt
1606                 
1607     def _printPolygons(self, mesh, sprite): 
1608         """Print the selected (visible) polygons.
1609         """
1610
1611         if len(mesh.faces) == 0:
1612             return
1613
1614         for face in mesh.faces:
1615             if not face.sel:
1616                continue
1617
1618             if face.col:
1619                 fcol = face.col[0]
1620                 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1621             else:
1622                 color = [255, 255, 255, 255]
1623
1624             s = SWFShape()
1625             f = s.addFill(color[0], color[1], color[2], color[3])
1626             s.setRightFill(f)
1627
1628             # The starting point of the shape
1629             p0 = self._calcCanvasCoord(face.verts[0])
1630             s.movePenTo(p0[0], p0[1])
1631
1632             for v in face.verts[1:]:
1633                 p = self._calcCanvasCoord(v)
1634                 s.drawLineTo(p[0], p[1])
1635             
1636             # Closing the shape
1637             s.drawLineTo(p0[0], p0[1])
1638
1639             s.end()
1640             sprite.add(s)
1641
1642
1643     def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1644         """Print the wireframe using mesh edges.
1645         """
1646
1647         stroke_width = config.edges['WIDTH']
1648         stroke_col = config.edges['COLOR']
1649
1650         s = SWFShape()
1651
1652         for e in mesh.edges:
1653
1654             # Next, we set the line width and color for our shape.
1655             s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1656             255)
1657             
1658             if e.sel == 0:
1659                 if showHiddenEdges == False:
1660                     continue
1661                 else:
1662                     # SWF does not support dashed lines natively, so -for now-
1663                     # draw hidden lines thinner and half-trasparent
1664                     s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1665                             stroke_col[2], 128)
1666
1667             p1 = self._calcCanvasCoord(e.v1)
1668             p2 = self._calcCanvasCoord(e.v2)
1669
1670             s.movePenTo(p1[0], p1[1])
1671             s.drawLineTo(p2[0], p2[1])
1672
1673         s.end()
1674         sprite.add(s)
1675             
1676
1677 ## PDF Writer
1678
1679 try:
1680     from reportlab.pdfgen import canvas
1681     PDFSupported = True
1682 except:
1683     PDFSupported = False
1684
1685 class PDFVectorWriter(VectorWriter):
1686     """A concrete class for writing PDF output.
1687     """
1688
1689     def __init__(self, fileName):
1690         """Simply call the parent Contructor.
1691         """
1692         VectorWriter.__init__(self, fileName)
1693
1694         self.canvas = None
1695
1696
1697     ##
1698     # Public Methods
1699     #
1700
1701     def open(self, startFrame=1, endFrame=1):
1702         """Do some initialization operations.
1703         """
1704         VectorWriter.open(self, startFrame, endFrame)
1705         size = (self.canvasSize[0], self.canvasSize[1])
1706         self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1707
1708     def close(self):
1709         """Do some finalization operation.
1710         """
1711         self.canvas.save()
1712
1713         # remember to call the close method of the parent
1714         VectorWriter.close(self)
1715
1716     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1717             showHiddenEdges=False):
1718         """Convert the scene representation to SVG.
1719         """
1720         context = scene.getRenderingContext()
1721         framenumber = context.currentFrame()
1722
1723         Objects = scene.getChildren()
1724
1725         for obj in Objects:
1726
1727             if(obj.getType() != 'Mesh'):
1728                 continue
1729
1730             mesh = obj.getData(mesh=1)
1731
1732             if doPrintPolygons:
1733                 self._printPolygons(mesh)
1734
1735             if doPrintEdges:
1736                 self._printEdges(mesh, showHiddenEdges)
1737             
1738         self.canvas.showPage()
1739     
1740     ##  
1741     # Private Methods
1742     #
1743     
1744     def _calcCanvasCoord(self, v):
1745         """Convert vertex in scene coordinates to canvas coordinates.
1746         """
1747
1748         pt = Vector([0, 0, 0])
1749         
1750         mW = float(self.canvasSize[0])/2.0
1751         mH = float(self.canvasSize[1])/2.0
1752
1753         # rescale to canvas size
1754         pt[0] = v.co[0]*mW + mW
1755         pt[1] = v.co[1]*mH + mH
1756         pt[2] = v.co[2]
1757          
1758         # For now we want (0,0) in the top-left corner of the canvas.
1759         # Mirror and translate along y
1760         pt[1] *= -1
1761         pt[1] += self.canvasSize[1]
1762         
1763         return pt
1764                 
1765     def _printPolygons(self, mesh): 
1766         """Print the selected (visible) polygons.
1767         """
1768
1769         if len(mesh.faces) == 0:
1770             return
1771
1772         for face in mesh.faces:
1773             if not face.sel:
1774                continue
1775
1776             if face.col:
1777                 fcol = face.col[0]
1778                 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1779                         fcol.a/255.0]
1780             else:
1781                 color = [1, 1, 1, 1]
1782
1783             self.canvas.setFillColorRGB(color[0], color[1], color[2])
1784             # For debug
1785             self.canvas.setStrokeColorRGB(0, 0, 0)
1786
1787             path = self.canvas.beginPath()
1788
1789             # The starting point of the path
1790             p0 = self._calcCanvasCoord(face.verts[0])
1791             path.moveTo(p0[0], p0[1])
1792
1793             for v in face.verts[1:]:
1794                 p = self._calcCanvasCoord(v)
1795                 path.lineTo(p[0], p[1])
1796             
1797             # Closing the shape
1798             path.close()
1799
1800             self.canvas.drawPath(path, stroke=0, fill=1)
1801
1802     def _printEdges(self, mesh, showHiddenEdges=False):
1803         """Print the wireframe using mesh edges.
1804         """
1805
1806         stroke_width = config.edges['WIDTH']
1807         stroke_col = config.edges['COLOR']
1808        
1809         self.canvas.setLineCap(1)
1810         self.canvas.setLineJoin(1)
1811         self.canvas.setLineWidth(stroke_width)
1812         self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1813             stroke_col[2]/255)
1814
1815         for e in mesh.edges:
1816
1817             self.canvas.setLineWidth(stroke_width)
1818
1819             if e.sel == 0:
1820                 if showHiddenEdges == False:
1821                     continue
1822                 else:
1823                     # PDF does not support dashed lines natively, so -for now-
1824                     # draw hidden lines thinner
1825                     self.canvas.setLineWidth(stroke_width/2.0)
1826
1827             p1 = self._calcCanvasCoord(e.v1)
1828             p2 = self._calcCanvasCoord(e.v2)
1829
1830             self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1831
1832
1833
1834 # ---------------------------------------------------------------------
1835 #
1836 ## Rendering Classes
1837 #
1838 # ---------------------------------------------------------------------
1839
1840 # A dictionary to collect different shading style methods
1841 shadingStyles = dict()
1842 shadingStyles['FLAT'] = None
1843 shadingStyles['TOON'] = None
1844
1845 # A dictionary to collect different edge style methods
1846 edgeStyles = dict()
1847 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1848 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1849
1850 # A dictionary to collect the supported output formats
1851 outputWriters = dict()
1852 outputWriters['SVG'] = SVGVectorWriter
1853 if SWFSupported:
1854     outputWriters['SWF'] = SWFVectorWriter
1855 if PDFSupported:
1856     outputWriters['PDF'] = PDFVectorWriter
1857
1858
1859 class Renderer:
1860     """Render a scene viewed from the active camera.
1861     
1862     This class is responsible of the rendering process, transformation and
1863     projection of the objects in the scene are invoked by the renderer.
1864
1865     The rendering is done using the active camera for the current scene.
1866     """
1867
1868     def __init__(self):
1869         """Make the rendering process only for the current scene by default.
1870
1871         We will work on a copy of the scene, to be sure that the current scene do
1872         not get modified in any way.
1873         """
1874
1875         # Render the current Scene, this should be a READ-ONLY property
1876         self._SCENE = Scene.GetCurrent()
1877         
1878         # Use the aspect ratio of the scene rendering context
1879         context = self._SCENE.getRenderingContext()
1880
1881         aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1882         self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1883                             float(context.aspectRatioY())
1884                             )
1885
1886         # Render from the currently active camera 
1887         #self.cameraObj = self._SCENE.getCurrentCamera()
1888
1889         self.lights = []
1890
1891
1892     ##
1893     # Public Methods
1894     #
1895
1896     def doRendering(self, outputWriter, animation=False):
1897         """Render picture or animation and write it out.
1898         
1899         The parameters are:
1900             - a Vector writer object that will be used to output the result.
1901             - a flag to tell if we want to render an animation or only the
1902               current frame.
1903         """
1904         
1905         context = self._SCENE.getRenderingContext()
1906         origCurrentFrame = context.currentFrame()
1907
1908         # Handle the animation case
1909         if not animation:
1910             startFrame = origCurrentFrame
1911             endFrame = startFrame
1912             outputWriter.open()
1913         else:
1914             startFrame = context.startFrame()
1915             endFrame = context.endFrame()
1916             outputWriter.open(startFrame, endFrame)
1917         
1918         # Do the rendering process frame by frame
1919         print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1920         for f in xrange(startFrame, endFrame+1):
1921             print "\n\nFrame: %d" % f
1922
1923             # FIXME To get the correct camera position we have to use +1 here.
1924             # Is there a bug somewhere in the Scene module?
1925             context.currentFrame(f+1)
1926             self.cameraObj = self._SCENE.getCurrentCamera()
1927
1928             # Use some temporary workspace, a full copy of the scene
1929             inputScene = self._SCENE.copy(2)
1930
1931             # To get the objects at this frame remove the +1 ...
1932             ctx = inputScene.getRenderingContext()
1933             ctx.currentFrame(f)
1934
1935
1936             # Get a projector for this camera.
1937             # NOTE: the projector wants object in world coordinates,
1938             # so we should remember to apply modelview transformations
1939             # _before_ we do projection transformations.
1940             self.proj = Projector(self.cameraObj, self.canvasRatio)
1941
1942             try:
1943                 renderedScene = self.doRenderScene(inputScene)
1944             except :
1945                 print "There was an error! Aborting."
1946                 import traceback
1947                 print traceback.print_exc()
1948
1949                 self._SCENE.makeCurrent()
1950                 Scene.unlink(inputScene)
1951                 del inputScene
1952                 return
1953
1954             outputWriter.printCanvas(renderedScene,
1955                     doPrintPolygons = config.polygons['SHOW'],
1956                     doPrintEdges    = config.edges['SHOW'],
1957                     showHiddenEdges = config.edges['SHOW_HIDDEN'])
1958             
1959             # delete the rendered scene
1960             self._SCENE.makeCurrent()
1961             Scene.unlink(renderedScene)
1962             del renderedScene
1963
1964         outputWriter.close()
1965         print "Done!"
1966         context.currentFrame(origCurrentFrame)
1967
1968
1969     def doRenderScene(self, workScene):
1970         """Control the rendering process.
1971         
1972         Here we control the entire rendering process invoking the operation
1973         needed to transform and project the 3D scene in two dimensions.
1974         """
1975         
1976         # global processing of the scene
1977
1978         self._filterHiddenObjects(workScene)
1979
1980         self._buildLightSetup(workScene)
1981
1982         self._doSceneClipping(workScene)
1983
1984         self._doConvertGeometricObjsToMesh(workScene)
1985
1986         if config.output['JOIN_OBJECTS']:
1987             self._joinMeshObjectsInScene(workScene)
1988
1989         self._doSceneDepthSorting(workScene)
1990         
1991         # Per object activities
1992
1993         Objects = workScene.getChildren()
1994
1995         print "Total Objects: %d" % len(Objects)
1996         for i,obj in enumerate(Objects):
1997             print "\n\n-------"
1998             print "Rendering Object: %d" % i
1999
2000             if obj.getType() != 'Mesh':
2001                 print "Only Mesh supported! - Skipping type:", obj.getType()
2002                 continue
2003
2004             print "Rendering: ", obj.getName()
2005
2006             mesh = obj.getData(mesh=1)
2007
2008             self._doModelingTransformation(mesh, obj.matrix)
2009
2010             self._doBackFaceCulling(mesh)
2011
2012
2013             # When doing HSR with NEWELL we may want to flip all normals
2014             # toward the viewer
2015             if config.polygons['HSR'] == "NEWELL":
2016                 for f in mesh.faces:
2017                     f.sel = 1-f.sel
2018                 mesh.flipNormals()
2019                 for f in mesh.faces:
2020                     f.sel = 1
2021
2022             self._doLighting(mesh)
2023
2024             # Do "projection" now so we perform further processing
2025             # in Normalized View Coordinates
2026             self._doProjection(mesh, self.proj)
2027
2028             self._doViewFrustumClipping(mesh)
2029
2030             self._doHiddenSurfaceRemoval(mesh)
2031
2032             self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2033
2034             # Update the object data, important! :)
2035             mesh.update()
2036
2037         return workScene
2038
2039
2040     ##
2041     # Private Methods
2042     #
2043
2044     # Utility methods
2045
2046     def _getObjPosition(self, obj):
2047         """Return the obj position in World coordinates.
2048         """
2049         return obj.matrix.translationPart()
2050
2051     def _cameraViewVector(self):
2052         """Get the View Direction form the camera matrix.
2053         """
2054         return Vector(self.cameraObj.matrix[2]).resize3D()
2055
2056
2057     # Faces methods
2058
2059     def _isFaceVisible(self, face):
2060         """Determine if a face of an object is visible from the current camera.
2061         
2062         The view vector is calculated from the camera location and one of the
2063         vertices of the face (expressed in World coordinates, after applying
2064         modelview transformations).
2065
2066         After those transformations we determine if a face is visible by
2067         computing the angle between the face normal and the view vector, this
2068         angle has to be between -90 and 90 degrees for the face to be visible.
2069         This corresponds somehow to the dot product between the two, if it
2070         results > 0 then the face is visible.
2071
2072         There is no need to normalize those vectors since we are only interested in
2073         the sign of the cross product and not in the product value.
2074
2075         NOTE: here we assume the face vertices are in WorldCoordinates, so
2076         please transform the object _before_ doing the test.
2077         """
2078
2079         normal = Vector(face.no)
2080         camPos = self._getObjPosition(self.cameraObj)
2081         view_vect = None
2082
2083         # View Vector in orthographics projections is the view Direction of
2084         # the camera
2085         if self.cameraObj.data.getType() == 1:
2086             view_vect = self._cameraViewVector()
2087
2088         # View vector in perspective projections can be considered as
2089         # the difference between the camera position and one point of
2090         # the face, we choose the farthest point from the camera.
2091         if self.cameraObj.data.getType() == 0:
2092             vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2093             view_vect = vv[1]
2094
2095
2096         # if d > 0 the face is visible from the camera
2097         d = view_vect * normal
2098         
2099         if d > 0:
2100             return True
2101         else:
2102             return False
2103
2104
2105     # Scene methods
2106
2107     def _filterHiddenObjects(self, scene):
2108         """Discard object that are on hidden layers in the scene.
2109         """
2110
2111         Objects = scene.getChildren()
2112
2113         visible_obj_list = [ obj for obj in Objects if
2114                 set(obj.layers).intersection(set(scene.getLayers())) ]
2115
2116         for o in Objects:
2117             if o not in visible_obj_list:
2118                 scene.unlink(o)
2119
2120         scene.update()
2121
2122
2123
2124     def _buildLightSetup(self, scene):
2125         # Get the list of lighting sources
2126         obj_lst = scene.getChildren()
2127         self.lights = [ o for o in obj_lst if o.getType() == 'Lamp' ]
2128
2129         # When there are no lights we use a default lighting source
2130         # that have the same position of the camera
2131         if len(self.lights) == 0:
2132             l = Lamp.New('Lamp')
2133             lobj = Object.New('Lamp')
2134             lobj.loc = self.cameraObj.loc
2135             lobj.link(l) 
2136             self.lights.append(lobj)
2137
2138
2139     def _doSceneClipping(self, scene):
2140         """Clip whole objects against the View Frustum.
2141
2142         For now clip away only objects according to their center position.
2143         """
2144
2145         cam_pos = self._getObjPosition(self.cameraObj)
2146         view_vect = self._cameraViewVector()
2147
2148         near = self.cameraObj.data.clipStart
2149         far  = self.cameraObj.data.clipEnd
2150
2151         aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2152         fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2153         fovy = fovy * 360.0/pi
2154
2155         Objects = scene.getChildren()
2156
2157         for o in Objects:
2158             if o.getType() != 'Mesh': continue;
2159
2160             """
2161             obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2162
2163             d = obj_vect*view_vect
2164             theta = AngleBetweenVecs(obj_vect, view_vect)
2165             
2166             # if the object is outside the view frustum, clip it away
2167             if (d < near) or (d > far) or (theta > fovy):
2168                 scene.unlink(o)
2169             """
2170
2171             # Use the object bounding box
2172             # (whose points are already in WorldSpace Coordinate)
2173
2174             bb = o.getBoundBox()
2175             
2176             points_outside = 0
2177             for p in bb:
2178                 p_vect = Vector(cam_pos) - Vector(p)
2179
2180                 d = p_vect * view_vect
2181                 theta = AngleBetweenVecs(p_vect, view_vect)
2182
2183                 # Is this point outside the view frustum?
2184                 if (d < near) or (d > far) or (theta > fovy):
2185                     points_outside += 1
2186
2187             # If the bb is all outside the view frustum we clip the whole
2188             # object away
2189             if points_outside == len(bb):
2190                 scene.unlink(o)
2191
2192
2193
2194     def _doConvertGeometricObjsToMesh(self, scene):
2195         """Convert all "geometric" objects to mesh ones.
2196         """
2197         geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2198         #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2199
2200         Objects = scene.getChildren()
2201
2202         objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2203         for obj in objList:
2204             old_obj = obj
2205             obj = self._convertToRawMeshObj(obj)
2206             scene.link(obj)
2207             scene.unlink(old_obj)
2208
2209
2210             # XXX Workaround for Text and Curve which have some normals
2211             # inverted when they are converted to Mesh, REMOVE that when
2212             # blender will fix that!!
2213             if old_obj.getType() in ['Curve', 'Text']:
2214                 me = obj.getData(mesh=1)
2215                 for f in me.faces: f.sel = 1;
2216                 for v in me.verts: v.sel = 1;
2217                 me.remDoubles(0)
2218                 me.triangleToQuad()
2219                 me.recalcNormals()
2220                 me.update()
2221
2222
2223     def _doSceneDepthSorting(self, scene):
2224         """Sort objects in the scene.
2225
2226         The object sorting is done accordingly to the object centers.
2227         """
2228
2229         c = self._getObjPosition(self.cameraObj)
2230
2231         by_obj_center_pos = (lambda o1, o2:
2232                 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2233                 cmp((self._getObjPosition(o1) - Vector(c)).length,
2234                     (self._getObjPosition(o2) - Vector(c)).length)
2235             )
2236
2237         # Implement sorting by bounding box, the object with the bb
2238         # nearest to the camera should be drawn as last.
2239         by_nearest_bbox_point = (lambda o1, o2:
2240                 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2241                 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2242                      min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2243                 )
2244             )
2245
2246         
2247         Objects = scene.getChildren()
2248
2249         #Objects.sort(by_obj_center_pos)
2250         Objects.sort(by_nearest_bbox_point)
2251         
2252         # update the scene
2253         for o in Objects:
2254             scene.unlink(o)
2255             scene.link(o)
2256
2257     def _joinMeshObjectsInScene(self, scene):
2258         """Merge all the Mesh Objects in a scene into a single Mesh Object.
2259         """
2260
2261         oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
2262
2263         # FIXME: Object.join() do not work if the list contains 1 object
2264         if len(oList) == 1:
2265             return
2266
2267         mesh = Mesh.New('BigOne')
2268         bigObj = Object.New('Mesh', 'BigOne')
2269         bigObj.link(mesh)
2270
2271         scene.link(bigObj)
2272
2273         try:
2274             bigObj.join(oList)
2275         except RuntimeError:
2276             print "\nWarning! - Can't Join Objects\n"
2277             scene.unlink(bigObj)
2278             return
2279         except TypeError:
2280             print "Objects Type error?"
2281         
2282         for o in oList:
2283             scene.unlink(o)
2284
2285         scene.update()
2286
2287  
2288     # Per object/mesh methods
2289
2290     def _convertToRawMeshObj(self, object):
2291         """Convert geometry based object to a mesh object.
2292         """
2293         me = Mesh.New('RawMesh_'+object.name)
2294         me.getFromObject(object.name)
2295
2296         newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2297         newObject.link(me)
2298
2299         # If the object has no materials set a default material
2300         if not me.materials:
2301             me.materials = [Material.New()]
2302             #for f in me.faces: f.mat = 0
2303
2304         newObject.setMatrix(object.getMatrix())
2305
2306         return newObject
2307
2308     def _doModelingTransformation(self, mesh, matrix):
2309         """Transform object coordinates to world coordinates.
2310
2311         This step is done simply applying to the object its tranformation
2312         matrix and recalculating its normals.
2313         """
2314         # XXX FIXME: blender do not transform normals in the right way when
2315         # there are negative scale values
2316         if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2317             print "WARNING: Negative scales, expect incorrect results!"
2318
2319         mesh.transform(matrix, True)
2320
2321     def _doBackFaceCulling(self, mesh):
2322         """Simple Backface Culling routine.
2323         
2324         At this level we simply do a visibility test face by face and then
2325         select the vertices belonging to visible faces.
2326         """
2327         
2328         # Select all vertices, so edges can be displayed even if there are no
2329         # faces
2330         for v in mesh.verts:
2331             v.sel = 1
2332         
2333         Mesh.Mode(Mesh.SelectModes['FACE'])
2334         # Loop on faces
2335         for f in mesh.faces:
2336             f.sel = 0
2337             if self._isFaceVisible(f):
2338                 f.sel = 1
2339
2340     def _doLighting(self, mesh):
2341         """Apply an Illumination and shading model to the object.
2342
2343         The model used is the Phong one, it may be inefficient,
2344         but I'm just learning about rendering and starting from Phong seemed
2345         the most natural way.
2346         """
2347
2348         # If the mesh has vertex colors already, use them,
2349         # otherwise turn them on and do some calculations
2350         if mesh.vertexColors:
2351             return
2352         mesh.vertexColors = 1
2353
2354         materials = mesh.materials
2355
2356         camPos = self._getObjPosition(self.cameraObj)
2357
2358         # We do per-face color calculation (FLAT Shading), we can easily turn
2359         # to a per-vertex calculation if we want to implement some shading
2360         # technique. For an example see:
2361         # http://www.miralab.unige.ch/papers/368.pdf
2362         for f in mesh.faces:
2363             if not f.sel:
2364                 continue
2365
2366             mat = None
2367             if materials:
2368                 mat = materials[f.mat]
2369
2370             # A new default material
2371             if mat == None:
2372                 mat = Material.New('defMat')
2373
2374             # Check if it is a shadeless material
2375             elif mat.getMode() & Material.Modes['SHADELESS']:
2376                 I = mat.getRGBCol()
2377                 # Convert to a value between 0 and 255
2378                 tmp_col = [ int(c * 255.0) for c in I]
2379
2380                 for c in f.col:
2381                     c.r = tmp_col[0]
2382                     c.g = tmp_col[1]
2383                     c.b = tmp_col[2]
2384                     #c.a = tmp_col[3]
2385
2386                 continue
2387
2388
2389             # do vertex color calculation
2390
2391             TotDiffSpec = Vector([0.0, 0.0, 0.0])
2392
2393             for l in self.lights:
2394                 light_obj = l
2395                 light_pos = self._getObjPosition(l)
2396                 light = light_obj.getData()
2397             
2398                 L = Vector(light_pos).normalize()
2399
2400                 V = (Vector(camPos) - Vector(f.cent)).normalize()
2401
2402                 N = Vector(f.no).normalize()
2403
2404                 if config.polygons['SHADING'] == 'TOON':
2405                     NL = ShadingUtils.toonShading(N*L)
2406                 else:
2407                     NL = (N*L)
2408
2409                 # Should we use NL instead of (N*L) here?
2410                 R = 2 * (N*L) * N - L
2411
2412                 Ip = light.getEnergy()
2413
2414                 # Diffuse co-efficient
2415                 kd = mat.getRef() * Vector(mat.getRGBCol())
2416                 for i in [0, 1, 2]:
2417                     kd[i] *= light.col[i]
2418
2419                 Idiff = Ip * kd * max(0, NL)
2420
2421
2422                 # Specular component
2423                 ks = mat.getSpec() * Vector(mat.getSpecCol())
2424                 ns = mat.getHardness()
2425                 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2426
2427                 TotDiffSpec += (Idiff+Ispec)
2428
2429
2430             # Ambient component
2431             Iamb = Vector(Blender.World.Get()[0].getAmb())
2432             ka = mat.getAmb()
2433
2434             # Emissive component (convert to a triplet)
2435             ki = Vector([mat.getEmit()]*3)
2436
2437             #I = ki + Iamb + (Idiff + Ispec)
2438             I = ki + (ka * Iamb) + TotDiffSpec
2439
2440
2441             # Set Alpha component
2442             I = list(I)
2443             I.append(mat.getAlpha())
2444
2445             # Clamp I values between 0 and 1
2446             I = [ min(c, 1) for c in I]
2447             I = [ max(0, c) for c in I]
2448
2449             # Convert to a value between 0 and 255
2450             tmp_col = [ int(c * 255.0) for c in I]
2451
2452             for c in f.col:
2453                 c.r = tmp_col[0]
2454                 c.g = tmp_col[1]
2455                 c.b = tmp_col[2]
2456                 c.a = tmp_col[3]
2457
2458     def _doProjection(self, mesh, projector):
2459         """Apply Viewing and Projection tranformations.
2460         """
2461
2462         for v in mesh.verts:
2463             p = projector.doProjection(v.co[:])
2464             v.co[0] = p[0]
2465             v.co[1] = p[1]
2466             v.co[2] = p[2]
2467
2468         #mesh.recalcNormals()
2469         #mesh.update()
2470
2471         # We could reeset Camera matrix, since now
2472         # we are in Normalized Viewing Coordinates,
2473         # but doung that would affect World Coordinate
2474         # processing for other objects
2475
2476         #self.cameraObj.data.type = 1
2477         #self.cameraObj.data.scale = 2.0
2478         #m = Matrix().identity()
2479         #self.cameraObj.setMatrix(m)
2480
2481     def _doViewFrustumClipping(self, mesh):
2482         """Clip faces against the View Frustum.
2483         """
2484
2485         # The Canonical View Volume, 8 vertices, and 6 faces,
2486         # We consider its face normals pointing outside
2487         
2488         v1 = NMesh.Vert(1, 1, -1)
2489         v2 = NMesh.Vert(1, -1, -1)
2490         v3 = NMesh.Vert(-1, -1, -1)
2491         v4 = NMesh.Vert(-1, 1, -1)
2492         v5 = NMesh.Vert(1, 1, 1)
2493         v6 = NMesh.Vert(1, -1, 1)
2494         v7 = NMesh.Vert(-1, -1, 1)
2495         v8 = NMesh.Vert(-1, 1, 1)
2496
2497         cvv = []
2498         f1 = NMesh.Face([v1, v4, v3, v2])
2499         cvv.append(f1)
2500         f2 = NMesh.Face([v5, v6, v7, v8])
2501         cvv.append(f2)
2502         f3 = NMesh.Face([v1, v2, v6, v5])
2503         cvv.append(f3)
2504         f4 = NMesh.Face([v2, v3, v7, v6])
2505         cvv.append(f4)
2506         f5 = NMesh.Face([v3, v4, v8, v7])
2507         cvv.append(f5)
2508         f6 = NMesh.Face([v4, v1, v5, v8])
2509         cvv.append(f6)
2510
2511         nmesh = NMesh.GetRaw(mesh.name)
2512         clippedfaces = nmesh.faces[:]
2513         facelist = clippedfaces[:]
2514
2515         for clipface in cvv:
2516
2517             clippedfaces = []
2518
2519             for f in facelist:
2520                 
2521                 newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2522
2523                 if not newfaces:
2524                     # Check if the face is all outside the view frustum
2525                     # TODO: Do this test before, it is more efficient
2526                     points_outside = 0
2527                     for v in f:
2528                         if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2529                             points_outside += 1
2530
2531                     if points_outside != len(f):
2532                         clippedfaces.append(f)
2533                 else:
2534                     for nf in newfaces:
2535                         for v in nf:
2536                             nmesh.verts.append(v)
2537
2538                         nf.mat = f.mat
2539                         nf.sel = f.sel
2540                         nf.col = [f.col[0]] * len(nf.v)
2541
2542                         clippedfaces.append(nf)
2543             facelist = clippedfaces[:]
2544
2545
2546         nmesh.faces = facelist
2547         nmesh.update()
2548         
2549
2550     # HSR routines
2551     def __simpleDepthSort(self, mesh):
2552         """Sort faces by the furthest vertex.
2553
2554         This simple mesthod is known also as the painter algorithm, and it
2555         solves HSR correctly only for convex meshes.
2556         """
2557
2558         #global progress
2559
2560         # The sorting requires circa n*log(n) steps
2561         n = len(mesh.faces)
2562         progress.setActivity("HSR: Painter", n*log(n))
2563
2564         by_furthest_z = (lambda f1, f2: progress.update() and
2565                 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2566                 )
2567
2568         # FIXME: using NMesh to sort faces. We should avoid that!
2569         nmesh = NMesh.GetRaw(mesh.name)
2570
2571         # remember that _higher_ z values mean further points
2572         nmesh.faces.sort(by_furthest_z)
2573         nmesh.faces.reverse()
2574
2575         nmesh.update()
2576
2577
2578     def __newellDepthSort(self, mesh):
2579         """Newell's depth sorting.
2580
2581         """
2582
2583         #global progress
2584
2585         # Find non planar quads and convert them to triangle
2586         #for f in mesh.faces:
2587         #    f.sel = 0
2588         #    if is_nonplanar_quad(f.v):
2589         #        print "NON QUAD??"
2590         #        f.sel = 1
2591
2592
2593         # Now reselect all faces
2594         for f in mesh.faces:
2595             f.sel = 1
2596         mesh.quadToTriangle()
2597
2598         # FIXME: using NMesh to sort faces. We should avoid that!
2599         nmesh = NMesh.GetRaw(mesh.name)
2600
2601         # remember that _higher_ z values mean further points
2602         nmesh.faces.sort(by_furthest_z)
2603         nmesh.faces.reverse()
2604
2605         # Begin depth sort tests
2606
2607         # use the smooth flag to set marked faces
2608         for f in nmesh.faces:
2609             f.smooth = 0
2610
2611         facelist = nmesh.faces[:]
2612         maplist = []
2613
2614
2615         # The steps are _at_least_ equal to len(facelist), we do not count the
2616         # feces coming out from splitting!!
2617         progress.setActivity("HSR: Newell", len(facelist))
2618         #progress.setQuiet(True)
2619
2620         
2621         while len(facelist):
2622             debug("\n----------------------\n")
2623             debug("len(facelits): %d\n" % len(facelist))
2624             P = facelist[0]
2625
2626             pSign = sign(P.normal[2])
2627
2628             # We can discard faces parallel to the view vector
2629             #if P.normal[2] == 0:
2630             #    facelist.remove(P)
2631             #    continue
2632
2633             split_done = 0
2634             face_marked = 0
2635
2636             for Q in facelist[1:]:
2637
2638                 debug("P.smooth: " + str(P.smooth) + "\n")
2639                 debug("Q.smooth: " + str(Q.smooth) + "\n")
2640                 debug("\n")
2641
2642                 qSign = sign(Q.normal[2])
2643                 # TODO: check also if Q is parallel??
2644  
2645                 # Test 0: We need to test only those Qs whose furthest vertex
2646                 # is closer to the observer than the closest vertex of P.
2647
2648                 zP = [v.co[2] for v in P.v]
2649                 zQ = [v.co[2] for v in Q.v]
2650                 notZOverlap = min(zP) > max(zQ) + EPS
2651
2652                 if notZOverlap:
2653                     debug("\nTest 0\n")
2654                     debug("NOT Z OVERLAP!\n")
2655                     if Q.smooth == 0:
2656                         # If Q is not marked then we can safely print P
2657                         break
2658                     else:
2659                         debug("met a marked face\n")
2660                         continue
2661
2662  
2663                 # Test 1: X extent overlapping
2664                 xP = [v.co[0] for v in P.v]
2665                 xQ = [v.co[0] for v in Q.v]
2666                 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2667                 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2668
2669                 if notXOverlap:
2670                     debug("\nTest 1\n")
2671                     debug("NOT X OVERLAP!\n")
2672                     continue
2673
2674
2675                 # Test 2: Y extent Overlapping
2676                 yP = [v.co[1] for v in P.v]
2677                 yQ = [v.co[1] for v in Q.v]
2678                 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2679                 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2680
2681                 if notYOverlap:
2682                     debug("\nTest 2\n")
2683                     debug("NOT Y OVERLAP!\n")
2684                     continue
2685                 
2686
2687                 # Test 3: P vertices are all behind the plane of Q
2688                 n = 0
2689                 for Pi in P:
2690                     d = qSign * HSR.Distance(Vector(Pi), Q)
2691                     if d <= EPS:
2692                         n += 1
2693                 pVerticesBehindPlaneQ = (n == len(P))
2694
2695                 if pVerticesBehindPlaneQ:
2696                     debug("\nTest 3\n")
2697                     debug("P BEHIND Q!\n")
2698                     continue
2699
2700
2701                 # Test 4: Q vertices in front of the plane of P
2702                 n = 0
2703                 for Qi in Q:
2704                     d = pSign * HSR.Distance(Vector(Qi), P)
2705                     if d >= -EPS:
2706                         n += 1
2707                 qVerticesInFrontPlaneP = (n == len(Q))
2708
2709                 if qVerticesInFrontPlaneP:
2710                     debug("\nTest 4\n")
2711                     debug("Q IN FRONT OF P!\n")
2712                     continue
2713
2714
2715                 # Test 5: Check if projections of polygons effectively overlap,
2716                 # in previous tests we checked only bounding boxes.
2717
2718                 #if not projectionsOverlap(P, Q):
2719                 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2720                     debug("\nTest 5\n")
2721                     debug("Projections do not overlap!\n")
2722                     continue
2723
2724                 # We still can't say if P obscures Q.
2725
2726                 # But if Q is marked we do a face-split trying to resolve a
2727                 # difficulty (maybe a visibility cycle).
2728                 if Q.smooth == 1:
2729                     # Split P or Q
2730                     debug("Possibly a cycle detected!\n")
2731                     debug("Split here!!\n")
2732
2733                     facelist = HSR.facesplit(P, Q, facelist, nmesh)
2734                     split_done = 1
2735                     break 
2736
2737                 # The question now is: Does Q obscure P?
2738
2739
2740                 # Test 3bis: Q vertices are all behind the plane of P
2741                 n = 0
2742                 for Qi in Q:
2743                     d = pSign * HSR.Distance(Vector(Qi), P)
2744                     if d <= EPS:
2745                         n += 1
2746                 qVerticesBehindPlaneP = (n == len(Q))
2747
2748                 if qVerticesBehindPlaneP:
2749                     debug("\nTest 3bis\n")
2750                     debug("Q BEHIND P!\n")
2751
2752
2753                 # Test 4bis: P vertices in front of the plane of Q
2754                 n = 0
2755                 for Pi in P:
2756                     d = qSign * HSR.Distance(Vector(Pi), Q)
2757                     if d >= -EPS:
2758                         n += 1
2759                 pVerticesInFrontPlaneQ = (n == len(P))
2760
2761                 if pVerticesInFrontPlaneQ:
2762                     debug("\nTest 4bis\n")
2763                     debug("P IN FRONT OF Q!\n")
2764
2765                 
2766                 # We don't even know if Q does obscure P, so they should
2767                 # intersect each other, split one of them in two parts.
2768                 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2769                     debug("\nSimple Intersection?\n")
2770                     debug("Test 3bis or 4bis failed\n")
2771                     debug("Split here!!2\n")
2772
2773                     facelist = HSR.facesplit(P, Q, facelist, nmesh)
2774                     split_done = 1
2775                     break 
2776                     
2777                 facelist.remove(Q)
2778                 facelist.insert(0, Q)
2779                 Q.smooth = 1
2780                 face_marked = 1
2781                 debug("Q marked!\n")
2782                 break
2783  
2784             # Write P!                     
2785             if split_done == 0 and face_marked == 0:
2786                 facelist.remove(P)
2787                 maplist.append(P)
2788                 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2789
2790                 progress.update()
2791
2792             if len(facelist) == 870:
2793                 dumpfaces([P, Q], "loopdebug.svg")
2794
2795
2796             #if facelist == None:
2797             #    maplist = [P, Q]
2798             #    print [v.co for v in P]
2799             #    print [v.co for v in Q]
2800             #    break
2801
2802             # end of while len(facelist)
2803          
2804
2805         nmesh.faces = maplist
2806         #for f in nmesh.faces:
2807         #    f.sel = 1
2808
2809         nmesh.update()
2810
2811
2812     def _doHiddenSurfaceRemoval(self, mesh):
2813         """Do HSR for the given mesh.
2814         """
2815         if len(mesh.faces) == 0:
2816             return
2817
2818         if config.polygons['HSR'] == 'PAINTER':
2819             print "\nUsing the Painter algorithm for HSR."
2820             self.__simpleDepthSort(mesh)
2821
2822         elif config.polygons['HSR'] == 'NEWELL':
2823             print "\nUsing the Newell's algorithm for HSR."
2824             self.__newellDepthSort(mesh)
2825
2826
2827     def _doEdgesStyle(self, mesh, edgestyleSelect):
2828         """Process Mesh Edges accroding to a given selection style.
2829
2830         Examples of algorithms:
2831
2832         Contours:
2833             given an edge if its adjacent faces have the same normal (that is
2834             they are complanar), than deselect it.
2835
2836         Silhouettes:
2837             given an edge if one its adjacent faces is frontfacing and the
2838             other is backfacing, than select it, else deselect.
2839         """
2840
2841         Mesh.Mode(Mesh.SelectModes['EDGE'])
2842
2843         edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2844
2845         for i,edge_faces in enumerate(edge_cache):
2846             mesh.edges[i].sel = 0
2847             if edgestyleSelect(edge_faces):
2848                 mesh.edges[i].sel = 1
2849
2850         """
2851         for e in mesh.edges:
2852
2853             e.sel = 0
2854             if edgestyleSelect(e, mesh):
2855                 e.sel = 1
2856         """
2857         #
2858
2859
2860 # ---------------------------------------------------------------------
2861 #
2862 ## GUI Class and Main Program
2863 #
2864 # ---------------------------------------------------------------------
2865
2866
2867 from Blender import BGL, Draw
2868 from Blender.BGL import *
2869
2870 class GUI:
2871     
2872     def _init():
2873
2874         # Output Format menu 
2875         output_format = config.output['FORMAT']
2876         default_value = outputWriters.keys().index(output_format)+1
2877         GUI.outFormatMenu = Draw.Create(default_value)
2878         GUI.evtOutFormatMenu = 0
2879
2880         # Animation toggle button
2881         GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2882         GUI.evtAnimToggle = 1
2883
2884         # Join Objects toggle button
2885         GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2886         GUI.evtJoinObjsToggle = 2
2887
2888         # Render filled polygons
2889         GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2890
2891         # Shading Style menu 
2892         shading_style = config.polygons['SHADING']
2893         default_value = shadingStyles.keys().index(shading_style)+1
2894         GUI.shadingStyleMenu = Draw.Create(default_value)
2895         GUI.evtShadingStyleMenu = 21
2896
2897         GUI.evtPolygonsToggle = 3
2898         # We hide the config.polygons['EXPANSION_TRICK'], for now
2899
2900         # Render polygon edges
2901         GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2902         GUI.evtShowEdgesToggle = 4
2903
2904         # Render hidden edges
2905         GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2906         GUI.evtShowHiddenEdgesToggle = 5
2907
2908         # Edge Style menu 
2909         edge_style = config.edges['STYLE']
2910         default_value = edgeStyles.keys().index(edge_style)+1
2911         GUI.edgeStyleMenu = Draw.Create(default_value)
2912         GUI.evtEdgeStyleMenu = 6
2913
2914         # Edge Width slider
2915         GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2916         GUI.evtEdgeWidthSlider = 7
2917
2918         # Edge Color Picker
2919         c = config.edges['COLOR']
2920         GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2921         GUI.evtEdgeColorPicker = 71
2922
2923         # Render Button
2924         GUI.evtRenderButton = 8
2925
2926         # Exit Button
2927         GUI.evtExitButton = 9
2928
2929     def draw():
2930
2931         # initialize static members
2932         GUI._init()
2933
2934         glClear(GL_COLOR_BUFFER_BIT)
2935         glColor3f(0.0, 0.0, 0.0)
2936         glRasterPos2i(10, 350)
2937         Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2938                 __version__)
2939         glRasterPos2i(10, 335)
2940         Draw.Text("Press Q or ESC to quit.")
2941
2942         # Build the output format menu
2943         glRasterPos2i(10, 310)
2944         Draw.Text("Select the output Format:")
2945         outMenuStruct = "Output Format %t"
2946         for t in outputWriters.keys():
2947            outMenuStruct = outMenuStruct + "|%s" % t
2948         GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2949                 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2950
2951         # Animation toggle
2952         GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2953                 10, 260, 160, 18, GUI.animToggle.val,
2954                 "Toggle rendering of animations")
2955
2956         # Join Objects toggle
2957         GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2958                 10, 235, 160, 18, GUI.joinObjsToggle.val,
2959                 "Join objects in the rendered file")
2960
2961         # Render Button
2962         Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2963                 "Start Rendering")
2964         Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2965
2966         # Rendering Styles
2967         glRasterPos2i(200, 310)
2968         Draw.Text("Rendering Style:")
2969
2970         # Render Polygons
2971         GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
2972                 200, 285, 160, 18, GUI.polygonsToggle.val,
2973                 "Render filled polygons")
2974
2975         if GUI.polygonsToggle.val == 1:
2976
2977             # Polygon Shading Style
2978             shadingStyleMenuStruct = "Shading Style %t"
2979             for t in shadingStyles.keys():
2980                 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
2981             GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
2982                     200, 260, 160, 18, GUI.shadingStyleMenu.val,
2983                     "Choose the shading style")
2984
2985
2986         # Render Edges
2987         GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
2988                 200, 235, 160, 18, GUI.showEdgesToggle.val,
2989                 "Render polygon edges")
2990
2991         if GUI.showEdgesToggle.val == 1:
2992             
2993             # Edge Style
2994             edgeStyleMenuStruct = "Edge Style %t"
2995             for t in edgeStyles.keys():
2996                 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
2997             GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
2998                     200, 210, 160, 18, GUI.edgeStyleMenu.val,
2999                     "Choose the edge style")
3000
3001             # Edge size
3002             GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
3003                     200, 185, 140, 18, GUI.edgeWidthSlider.val,
3004                     0.0, 10.0, 0, "Change Edge Width")
3005
3006             # Edge Color
3007             GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
3008                     342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
3009
3010             # Show Hidden Edges
3011             GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
3012                     GUI.evtShowHiddenEdgesToggle,
3013                     200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
3014                     "Render hidden edges as dashed lines")
3015
3016         glRasterPos2i(10, 160)
3017         Draw.Text("%s (c) 2006" % __author__)
3018
3019     def event(evt, val):
3020
3021         if evt == Draw.ESCKEY or evt == Draw.QKEY:
3022             Draw.Exit()
3023         else:
3024             return
3025
3026         Draw.Redraw(1)
3027
3028     def button_event(evt):
3029
3030         if evt == GUI.evtExitButton:
3031             Draw.Exit()
3032
3033         elif evt == GUI.evtOutFormatMenu:
3034             i = GUI.outFormatMenu.val - 1
3035             config.output['FORMAT']= outputWriters.keys()[i]
3036             # Set the new output file
3037             global outputfile
3038             outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3039
3040         elif evt == GUI.evtAnimToggle:
3041             config.output['ANIMATION'] = bool(GUI.animToggle.val)
3042
3043         elif evt == GUI.evtJoinObjsToggle:
3044             config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3045
3046         elif evt == GUI.evtPolygonsToggle:
3047             config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3048
3049         elif evt == GUI.evtShadingStyleMenu:
3050             i = GUI.shadingStyleMenu.val - 1
3051             config.polygons['SHADING'] = shadingStyles.keys()[i]
3052
3053         elif evt == GUI.evtShowEdgesToggle:
3054             config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3055
3056         elif evt == GUI.evtShowHiddenEdgesToggle:
3057             config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3058
3059         elif evt == GUI.evtEdgeStyleMenu:
3060             i = GUI.edgeStyleMenu.val - 1
3061             config.edges['STYLE'] = edgeStyles.keys()[i]
3062
3063         elif evt == GUI.evtEdgeWidthSlider:
3064             config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3065
3066         elif evt == GUI.evtEdgeColorPicker:
3067             config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3068
3069         elif evt == GUI.evtRenderButton:
3070             label = "Save %s" % config.output['FORMAT']
3071             # Show the File Selector
3072             global outputfile
3073             Blender.Window.FileSelector(vectorize, label, outputfile)
3074
3075         else:
3076             print "Event: %d not handled!" % evt
3077
3078         if evt:
3079             Draw.Redraw(1)
3080             #GUI.conf_debug()
3081
3082     def conf_debug():
3083         from pprint import pprint
3084         print "\nConfig"
3085         pprint(config.output)
3086         pprint(config.polygons)
3087         pprint(config.edges)
3088
3089     _init = staticmethod(_init)
3090     draw = staticmethod(draw)
3091     event = staticmethod(event)
3092     button_event = staticmethod(button_event)
3093     conf_debug = staticmethod(conf_debug)
3094
3095 # A wrapper function for the vectorizing process
3096 def vectorize(filename):
3097     """The vectorizing process is as follows:
3098      
3099      - Instanciate the writer and the renderer
3100      - Render!
3101      """
3102
3103     if filename == "":
3104         print "\nERROR: invalid file name!"
3105         return
3106
3107     from Blender import Window
3108     editmode = Window.EditMode()
3109     if editmode: Window.EditMode(0)
3110
3111     actualWriter = outputWriters[config.output['FORMAT']]
3112     writer = actualWriter(filename)
3113     
3114     renderer = Renderer()
3115     renderer.doRendering(writer, config.output['ANIMATION'])
3116
3117     if editmode: Window.EditMode(1) 
3118
3119
3120
3121 # Here the main
3122 if __name__ == "__main__":
3123
3124     global progress
3125
3126     outputfile = ""
3127     basename = Blender.sys.basename(Blender.Get('filename'))
3128     if basename != "":
3129         outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3130
3131     if Blender.mode == 'background':
3132         progress = ConsoleProgressIndicator()
3133         vectorize(outputfile)
3134     else:
3135         progress = GraphicalProgressIndicator()
3136         Draw.Register(GUI.draw, GUI.event, GUI.button_event)