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