Misc fixes and improvements
[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"
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 #   - Use multiple lighting sources in color calculation,
46 #     (this is part of the "shading refactor") and use light color!
47 #   - FIX the issue with negative scales in object tranformations!
48 #   - Use a better depth sorting algorithm
49 #   - Implement clipping of primitives and do handle object intersections.
50 #     (for now only clipping away whole objects is supported).
51 #   - Review how selections are made (this script uses selection states of
52 #     primitives to represent visibility infos)
53 #   - Use a data structure other than Mesh to represent the 2D image? 
54 #     Think to a way to merge (adjacent) polygons that have the same color.
55 #     Or a way to use paths for silhouettes and contours.
56 #   - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
57 #     not support SMIL for animations)
58 #   - Switch to the Mesh structure, should be considerably faster
59 #    (partially done, but with Mesh we cannot sort faces, yet)
60 #   - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
61 #   - Implement Shading Styles? (for now we use Flat Shading) (partially done).
62 #   - Add Vector Writers other than SVG.
63 #
64 # ---------------------------------------------------------------------
65 #
66 # Changelog:
67 #
68 #   vrm-0.3.py  -   2006-05-19
69 #    * First release after code restucturing.
70 #      Now the script offers a useful set of functionalities
71 #      and it can render animations, too.
72 #
73 # ---------------------------------------------------------------------
74
75 import Blender
76 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera
77 from Blender.Mathutils import *
78 from math import *
79
80
81 # Some global settings
82
83 class config:
84     polygons = dict()
85     polygons['SHOW'] = True
86     polygons['SHADING'] = 'TOON'
87     # Hidden to the user for now
88     polygons['EXPANSION_TRICK'] = True
89
90     edges = dict()
91     edges['SHOW'] = True
92     edges['SHOW_HIDDEN'] = False
93     edges['STYLE'] = 'SILHOUETTE'
94     edges['WIDTH'] = 2
95     edges['COLOR'] = [0, 0, 0]
96
97     output = dict()
98     output['FORMAT'] = 'SVG'
99     output['ANIMATION'] = False
100     output['JOIN_OBJECTS'] = True
101
102
103
104 # ---------------------------------------------------------------------
105 #
106 ## Utility Mesh class
107 #
108 # ---------------------------------------------------------------------
109 class MeshUtils:
110
111     def getEdgeAdjacentFaces(edge, mesh):
112         """Get the faces adjacent to a given edge.
113
114         There can be 0, 1 or more (usually 2) faces adjacent to an edge.
115         """
116         adjface_list = []
117
118         for f in mesh.faces:
119             if (edge.v1 in f.v) and (edge.v2 in f.v):
120                 adjface_list.append(f)
121
122         return adjface_list
123
124     def isMeshEdge(e, mesh):
125         """Mesh edge rule.
126
127         A mesh edge is visible if _any_ of its adjacent faces is selected.
128         Note: if the edge has no adjacent faces we want to show it as well,
129         useful for "edge only" portion of objects.
130         """
131
132         adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
133
134         if len(adjacent_faces) == 0:
135             return True
136
137         selected_faces = [f for f in adjacent_faces if f.sel]
138
139         if len(selected_faces) != 0:
140             return True
141         else:
142             return False
143
144     def isSilhouetteEdge(e, mesh):
145         """Silhuette selection rule.
146
147         An edge is a silhuette edge if it is shared by two faces with
148         different selection status or if it is a boundary edge of a selected
149         face.
150         """
151
152         adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
153
154         if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
155             (len(adjacent_faces) == 2 and
156                 adjacent_faces[0].sel != adjacent_faces[1].sel)
157             ):
158             return True
159         else:
160             return False
161     
162     def toonShading(u):
163
164         levels = 2
165         texels = 2*levels - 1
166         map = [0.0] + [(i)/float(texels-1) for i in range(1, texels-1) ] + [1.0]
167         
168         v = 1.0
169         for i in range(0, len(map)-1):
170             pivot = (map[i]+map[i+1])/2.0
171             j = int(u>pivot)
172
173             v = map[i+j]
174
175             if v<map[i+1]:
176                 return v
177
178         return v
179
180
181     getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces)
182     isMeshEdge = staticmethod(isMeshEdge)
183     isSilhouetteEdge = staticmethod(isSilhouetteEdge)
184     toonShading = staticmethod(toonShading)
185
186
187
188 # ---------------------------------------------------------------------
189 #
190 ## Projections classes
191 #
192 # ---------------------------------------------------------------------
193
194 class Projector:
195     """Calculate the projection of an object given the camera.
196     
197     A projector is useful to so some per-object transformation to obtain the
198     projection of an object given the camera.
199     
200     The main method is #doProjection# see the method description for the
201     parameter list.
202     """
203
204     def __init__(self, cameraObj, canvasRatio):
205         """Calculate the projection matrix.
206
207         The projection matrix depends, in this case, on the camera settings.
208         TAKE CARE: This projector expects vertices in World Coordinates!
209         """
210
211         camera = cameraObj.getData()
212
213         aspect = float(canvasRatio[0])/float(canvasRatio[1])
214         near = camera.clipStart
215         far = camera.clipEnd
216
217         scale = float(camera.scale)
218
219         fovy = atan(0.5/aspect/(camera.lens/32))
220         fovy = fovy * 360.0/pi
221         
222         # What projection do we want?
223         if camera.type == 0:
224             mP = self._calcPerspectiveMatrix(fovy, aspect, near, far) 
225         elif camera.type == 1:
226             mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale) 
227         
228         # View transformation
229         cam = Matrix(cameraObj.getInverseMatrix())
230         cam.transpose() 
231         
232         mP = mP * cam
233
234         self.projectionMatrix = mP
235
236     ##
237     # Public methods
238     #
239
240     def doProjection(self, v):
241         """Project the point on the view plane.
242
243         Given a vertex calculate the projection using the current projection
244         matrix.
245         """
246         
247         # Note that we have to work on the vertex using homogeneous coordinates
248         p = self.projectionMatrix * Vector(v).resize4D()
249
250         # Perspective division
251         if p[3] != 0:
252             p[0] = p[0]/p[3]
253             p[1] = p[1]/p[3]
254             p[2] = p[2]/p[3]
255
256         # restore the size
257         p[3] = 1.0
258         p.resize3D()
259
260         return p
261
262
263     ##
264     # Private methods
265     #
266     
267     def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
268         """Return a perspective projection matrix.
269         """
270         
271         top = near * tan(fovy * pi / 360.0)
272         bottom = -top
273         left = bottom*aspect
274         right= top*aspect
275         x = (2.0 * near) / (right-left)
276         y = (2.0 * near) / (top-bottom)
277         a = (right+left) / (right-left)
278         b = (top+bottom) / (top - bottom)
279         c = - ((far+near) / (far-near))
280         d = - ((2*far*near)/(far-near))
281         
282         m = Matrix(
283                 [x,   0.0,    a,    0.0],
284                 [0.0,   y,    b,    0.0],
285                 [0.0, 0.0,    c,      d],
286                 [0.0, 0.0, -1.0,    0.0])
287
288         return m
289
290     def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
291         """Return an orthogonal projection matrix.
292         """
293         
294         # The 11 in the formula was found emiprically
295         top = near * tan(fovy * pi / 360.0) * (scale * 11)
296         bottom = -top 
297         left = bottom * aspect
298         right= top * aspect
299         rl = right-left
300         tb = top-bottom
301         fn = near-far 
302         tx = -((right+left)/rl)
303         ty = -((top+bottom)/tb)
304         tz = ((far+near)/fn)
305
306         m = Matrix(
307                 [2.0/rl, 0.0,    0.0,     tx],
308                 [0.0,    2.0/tb, 0.0,     ty],
309                 [0.0,    0.0,    2.0/fn,  tz],
310                 [0.0,    0.0,    0.0,    1.0])
311         
312         return m
313
314
315
316 # ---------------------------------------------------------------------
317 #
318 ## 2D Object representation class
319 #
320 # ---------------------------------------------------------------------
321
322 # TODO: a class to represent the needed properties of a 2D vector image
323 # For now just using a [N]Mesh structure.
324
325
326 # ---------------------------------------------------------------------
327 #
328 ## Vector Drawing Classes
329 #
330 # ---------------------------------------------------------------------
331
332 ## A generic Writer
333
334 class VectorWriter:
335     """
336     A class for printing output in a vectorial format.
337
338     Given a 2D representation of the 3D scene the class is responsible to
339     write it is a vector format.
340
341     Every subclasses of VectorWriter must have at last the following public
342     methods:
343         - open(self)
344         - close(self)
345         - printCanvas(self, scene,
346             doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
347     """
348     
349     def __init__(self, fileName):
350         """Set the output file name and other properties"""
351
352         self.outputFileName = fileName
353         self.file = None
354         
355         context = Scene.GetCurrent().getRenderingContext()
356         self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
357
358         self.startFrame = 1
359         self.endFrame = 1
360         self.animation = False
361
362
363     ##
364     # Public Methods
365     #
366     
367     def open(self, startFrame=1, endFrame=1):
368         if startFrame != endFrame:
369             self.startFrame = startFrame
370             self.endFrame = endFrame
371             self.animation = True
372
373         self.file = open(self.outputFileName, "w")
374         print "Outputting to: ", self.outputFileName
375
376         return
377
378     def close(self):
379         self.file.close()
380         return
381
382     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
383             showHiddenEdges=False):
384         """This is the interface for the needed printing routine.
385         """
386         return
387         
388
389 ## SVG Writer
390
391 class SVGVectorWriter(VectorWriter):
392     """A concrete class for writing SVG output.
393     """
394
395     def __init__(self, fileName):
396         """Simply call the parent Contructor.
397         """
398         VectorWriter.__init__(self, fileName)
399
400
401     ##
402     # Public Methods
403     #
404
405     def open(self, startFrame=1, endFrame=1):
406         """Do some initialization operations.
407         """
408         VectorWriter.open(self, startFrame, endFrame)
409         self._printHeader()
410
411     def close(self):
412         """Do some finalization operation.
413         """
414         self._printFooter()
415
416         # remember to call the close method of the parent
417         VectorWriter.close(self)
418
419         
420     def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
421             showHiddenEdges=False):
422         """Convert the scene representation to SVG.
423         """
424
425         Objects = scene.getChildren()
426
427         context = scene.getRenderingContext()
428         framenumber = context.currentFrame()
429
430         if self.animation:
431             framestyle = "display:none"
432         else:
433             framestyle = "display:block"
434         
435         # Assign an id to this group so we can set properties on it using DOM
436         self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
437                 (framenumber, framestyle) )
438
439
440         for obj in Objects:
441
442             if(obj.getType() != 'Mesh'):
443                 continue
444
445             self.file.write("<g id=\"%s\">\n" % obj.getName())
446
447             mesh = obj.getData(mesh=1)
448
449             if doPrintPolygons:
450                 self._printPolygons(mesh)
451
452             if doPrintEdges:
453                 self._printEdges(mesh, showHiddenEdges)
454             
455             self.file.write("</g>\n")
456
457         self.file.write("</g>\n")
458
459     
460     ##  
461     # Private Methods
462     #
463     
464     def _calcCanvasCoord(self, v):
465         """Convert vertex in scene coordinates to canvas coordinates.
466         """
467
468         pt = Vector([0, 0, 0])
469         
470         mW = float(self.canvasSize[0])/2.0
471         mH = float(self.canvasSize[1])/2.0
472
473         # rescale to canvas size
474         pt[0] = v.co[0]*mW + mW
475         pt[1] = v.co[1]*mH + mH
476         pt[2] = v.co[2]
477          
478         # For now we want (0,0) in the top-left corner of the canvas.
479         # Mirror and translate along y
480         pt[1] *= -1
481         pt[1] += self.canvasSize[1]
482         
483         return pt
484
485     def _printHeader(self):
486         """Print SVG header."""
487
488         self.file.write("<?xml version=\"1.0\"?>\n")
489         self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n")
490         self.file.write("\t\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
491         self.file.write("<svg version=\"1.1\"\n")
492         self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
493         self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
494                 self.canvasSize)
495
496         if self.animation:
497
498             self.file.write("""\n<script><![CDATA[
499             globalStartFrame=%d;
500             globalEndFrame=%d;
501
502             /* FIXME: Use 1000 as interval as lower values gives problems */
503             timerID = setInterval("NextFrame()", 1000);
504             globalFrameCounter=%d;
505
506             function NextFrame()
507             {
508               currentElement  = document.getElementById('frame'+globalFrameCounter)
509               previousElement = document.getElementById('frame'+(globalFrameCounter-1))
510
511               if (!currentElement)
512               {
513                 return;
514               }
515
516               if (globalFrameCounter > globalEndFrame)
517               {
518                 clearInterval(timerID)
519               }
520               else
521               {
522                 if(previousElement)
523                 {
524                     previousElement.style.display="none";
525                 }
526                 currentElement.style.display="block";
527                 globalFrameCounter++;
528               }
529             }
530             \n]]></script>\n
531             \n""" % (self.startFrame, self.endFrame, self.startFrame) )
532                 
533     def _printFooter(self):
534         """Print the SVG footer."""
535
536         self.file.write("\n</svg>\n")
537
538     def _printPolygons(self, mesh): 
539         """Print the selected (visible) polygons.
540         """
541
542         if len(mesh.faces) == 0:
543             return
544
545         self.file.write("<g>\n")
546
547         for face in mesh.faces:
548             if not face.sel:
549                continue
550
551             self.file.write("<path d=\"")
552
553             p = self._calcCanvasCoord(face.verts[0])
554             self.file.write("M %g,%g L " % (p[0], p[1]))
555
556             for v in face.verts[1:]:
557                 p = self._calcCanvasCoord(v)
558                 self.file.write("%g,%g " % (p[0], p[1]))
559             
560             # get rid of the last blank space, just cosmetics here.
561             self.file.seek(-1, 1) 
562             self.file.write("\"\n")
563             
564             # take as face color the first vertex color
565             # TODO: the average of vetrex colors?
566             if face.col:
567                 fcol = face.col[0]
568                 color = [fcol.r, fcol.g, fcol.b, fcol.a]
569             else:
570                 color = [255, 255, 255, 255]
571
572             # Convert the color to the #RRGGBB form
573             str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
574
575             # use the stroke property to alleviate the "adjacent edges" problem,
576             # we simulate polygon expansion using borders,
577             # see http://www.antigrain.com/svg/index.html for more info
578             stroke_width = 0.5
579
580             # Handle transparent polygons
581             opacity_string = ""
582             if color[3] != 255:
583                 opacity = float(color[3])/255.0
584                 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
585
586             self.file.write("\tstyle=\"fill:" + str_col + ";")
587             self.file.write(opacity_string)
588             if config.polygons['EXPANSION_TRICK']:
589                 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
590                 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
591             self.file.write("\"/>\n")
592
593         self.file.write("</g>\n")
594
595     def _printEdges(self, mesh, showHiddenEdges=False):
596         """Print the wireframe using mesh edges.
597         """
598
599         stroke_width = config.edges['WIDTH']
600         stroke_col = config.edges['COLOR']
601         
602         self.file.write("<g>\n")
603
604         for e in mesh.edges:
605             
606             hidden_stroke_style = ""
607             
608             if e.sel == 0:
609                 if showHiddenEdges == False:
610                     continue
611                 else:
612                     hidden_stroke_style = ";\n stroke-dasharray:3, 3"
613
614             p1 = self._calcCanvasCoord(e.v1)
615             p2 = self._calcCanvasCoord(e.v2)
616             
617             self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
618                     % ( p1[0], p1[1], p2[0], p2[1] ) )
619             self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
620             self.file.write(" stroke-width:"+str(stroke_width)+";\n")
621             self.file.write(" stroke-linecap:round;stroke-linejoin:round")
622             self.file.write(hidden_stroke_style)
623             self.file.write("\"/>\n")
624
625         self.file.write("</g>\n")
626
627
628
629 # ---------------------------------------------------------------------
630 #
631 ## Rendering Classes
632 #
633 # ---------------------------------------------------------------------
634
635 # A dictionary to collect different shading style methods
636 shadingStyles = dict()
637 shadingStyles['FLAT'] = None
638 shadingStyles['TOON'] = None
639
640 # A dictionary to collect different edge style methods
641 edgeStyles = dict()
642 edgeStyles['MESH'] = MeshUtils.isMeshEdge
643 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
644
645 # A dictionary to collect the supported output formats
646 outputWriters = dict()
647 outputWriters['SVG'] = SVGVectorWriter
648
649
650 class Renderer:
651     """Render a scene viewed from a given camera.
652     
653     This class is responsible of the rendering process, transformation and
654     projection of the objects in the scene are invoked by the renderer.
655
656     The rendering is done using the active camera for the current scene.
657     """
658
659     def __init__(self):
660         """Make the rendering process only for the current scene by default.
661
662         We will work on a copy of the scene, be sure that the current scene do
663         not get modified in any way.
664         """
665
666         # Render the current Scene, this should be a READ-ONLY property
667         self._SCENE = Scene.GetCurrent()
668         
669         # Use the aspect ratio of the scene rendering context
670         context = self._SCENE.getRenderingContext()
671
672         aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
673         self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
674                             float(context.aspectRatioY())
675                             )
676
677         # Render from the currently active camera 
678         self.cameraObj = self._SCENE.getCurrentCamera()
679
680         # Get a projector for this camera.
681         # NOTE: the projector wants object in world coordinates,
682         # so we should remember to apply modelview transformations
683         # _before_ we do projection transformations.
684         self.proj = Projector(self.cameraObj, self.canvasRatio)
685
686         # Get the list of lighting sources
687         obj_lst = self._SCENE.getChildren()
688         self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
689
690         # When there are no lights we use a default lighting source
691         # that have the same position of the camera
692         if len(self.lights) == 0:
693             l = Lamp.New('Lamp')
694             lobj = Object.New('Lamp')
695             lobj.loc = self.cameraObj.loc
696             lobj.link(l) 
697             self.lights.append(lobj)
698
699
700     ##
701     # Public Methods
702     #
703
704     def doRendering(self, outputWriter, animation=False):
705         """Render picture or animation and write it out.
706         
707         The parameters are:
708             - a Vector writer object that will be used to output the result.
709             - a flag to tell if we want to render an animation or only the
710               current frame.
711         """
712         
713         context = self._SCENE.getRenderingContext()
714         origCurrentFrame = context.currentFrame()
715
716         # Handle the animation case
717         if not animation:
718             startFrame = origCurrentFrame
719             endFrame = startFrame
720             outputWriter.open()
721         else:
722             startFrame = context.startFrame()
723             endFrame = context.endFrame()
724             outputWriter.open(startFrame, endFrame)
725         
726         # Do the rendering process frame by frame
727         print "Start Rendering!"
728         for f in range(startFrame, endFrame+1):
729             context.currentFrame(f)
730
731             # Use some temporary workspace, a full copy of the scene
732             inputScene = self._SCENE.copy(2)
733             # And Set our camera accordingly
734             self.cameraObj = inputScene.getCurrentCamera()
735
736             try:
737                 renderedScene = self.doRenderScene(inputScene)
738             except :
739                 print "There was an error! Aborting."
740                 import traceback
741                 print traceback.print_exc()
742
743                 self._SCENE.makeCurrent()
744                 Scene.unlink(inputScene)
745                 del inputScene
746                 return
747
748             outputWriter.printCanvas(renderedScene,
749                     doPrintPolygons = config.polygons['SHOW'],
750                     doPrintEdges    = config.edges['SHOW'],
751                     showHiddenEdges = config.edges['SHOW_HIDDEN'])
752             
753             # clear the rendered scene
754             self._SCENE.makeCurrent()
755             #Scene.unlink(renderedScene)
756             #del renderedScene
757
758         outputWriter.close()
759         print "Done!"
760         context.currentFrame(origCurrentFrame)
761
762
763     def doRenderScene(self, workScene):
764         """Control the rendering process.
765         
766         Here we control the entire rendering process invoking the operation
767         needed to transform and project the 3D scene in two dimensions.
768         """
769         
770         # global processing of the scene
771
772         self._doSceneClipping(workScene)
773
774         self._doConvertGeometricObjToMesh(workScene)
775
776         if config.output['JOIN_OBJECTS']:
777             self._joinMeshObjectsInScene(workScene)
778
779         self._doSceneDepthSorting(workScene)
780         
781         # Per object activities
782
783         Objects = workScene.getChildren()
784         for obj in Objects:
785
786             if obj.getType() != 'Mesh':
787                 print "Only Mesh supported! - Skipping type:", obj.getType()
788                 continue
789
790             print "Rendering: ", obj.getName()
791
792             mesh = obj.getData(mesh=1)
793
794             self._doModelingTransformation(mesh, obj.matrix)
795
796             self._doBackFaceCulling(mesh)
797
798             self._doPerVertexLighting(mesh)
799
800             # Do "projection" now so we perform further processing
801             # in Normalized View Coordinates
802             self._doProjection(mesh, self.proj)
803
804             self._doViewFrustumClipping(mesh)
805
806             self._doMeshDepthSorting(mesh)
807
808             self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
809
810             
811             # Update the object data, important! :)
812             mesh.update()
813
814         return workScene
815
816
817     ##
818     # Private Methods
819     #
820
821     # Utility methods
822
823     def _getObjPosition(self, obj):
824         """Return the obj position in World coordinates.
825         """
826         return obj.matrix.translationPart()
827
828     def _cameraViewVector(self):
829         """Get the View Direction form the camera matrix.
830         """
831         return Vector(self.cameraObj.matrix[2]).resize3D()
832
833
834     # Faces methods
835
836     def _isFaceVisible(self, face):
837         """Determine if a face of an object is visible from the current camera.
838         
839         The view vector is calculated from the camera location and one of the
840         vertices of the face (expressed in World coordinates, after applying
841         modelview transformations).
842
843         After those transformations we determine if a face is visible by
844         computing the angle between the face normal and the view vector, this
845         angle has to be between -90 and 90 degrees for the face to be visible.
846         This corresponds somehow to the dot product between the two, if it
847         results > 0 then the face is visible.
848
849         There is no need to normalize those vectors since we are only interested in
850         the sign of the cross product and not in the product value.
851
852         NOTE: here we assume the face vertices are in WorldCoordinates, so
853         please transform the object _before_ doing the test.
854         """
855
856         normal = Vector(face.no)
857         camPos = self._getObjPosition(self.cameraObj)
858         view_vect = None
859
860         # View Vector in orthographics projections is the view Direction of
861         # the camera
862         if self.cameraObj.data.getType() == 1:
863             view_vect = self._cameraViewVector()
864
865         # View vector in perspective projections can be considered as
866         # the difference between the camera position and one point of
867         # the face, we choose the farthest point from the camera.
868         if self.cameraObj.data.getType() == 0:
869             vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
870             view_vect = vv[1]
871
872
873         # if d > 0 the face is visible from the camera
874         d = view_vect * normal
875         
876         if d > 0:
877             return True
878         else:
879             return False
880
881
882     # Scene methods
883
884     def _doSceneClipping(self, scene):
885         """Clip whole objects against the View Frustum.
886
887         For now clip away only objects according to their center position.
888         """
889
890         cpos = self._getObjPosition(self.cameraObj)
891         view_vect = self._cameraViewVector()
892
893         near = self.cameraObj.data.clipStart
894         far  = self.cameraObj.data.clipEnd
895
896         aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
897         fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
898         fovy = fovy * 360.0/pi
899
900         Objects = scene.getChildren()
901         for o in Objects:
902             if o.getType() != 'Mesh': continue;
903
904             obj_vect = Vector(cpos) - self._getObjPosition(o)
905
906             d = obj_vect*view_vect
907             theta = AngleBetweenVecs(obj_vect, view_vect)
908             
909             # if the object is outside the view frustum, clip it away
910             if (d < near) or (d > far) or (theta > fovy):
911                 scene.unlink(o)
912
913     def _doConvertGeometricObjToMesh(self, scene):
914         """Convert all "geometric" objects to mesh ones.
915         """
916         geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
917
918         Objects = scene.getChildren()
919         objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
920         for obj in objList:
921             old_obj = obj
922             obj = self._convertToRawMeshObj(obj)
923             scene.link(obj)
924             scene.unlink(old_obj)
925
926
927             # XXX Workaround for Text and Curve which have some normals
928             # inverted when they are converted to Mesh, REMOVE that when
929             # blender will fix that!!
930             if old_obj.getType() in ['Curve', 'Text']:
931                 me = obj.getData(mesh=1)
932                 for f in me.faces: f.sel = 1;
933                 for v in me.verts: v.sel = 1;
934                 me.remDoubles(0)
935                 me.triangleToQuad()
936                 me.recalcNormals()
937                 me.update()
938
939
940     def _doSceneDepthSorting(self, scene):
941         """Sort objects in the scene.
942
943         The object sorting is done accordingly to the object centers.
944         """
945
946         c = self._getObjPosition(self.cameraObj)
947
948         by_center_pos = (lambda o1, o2:
949                 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
950                 cmp((self._getObjPosition(o1) - Vector(c)).length,
951                     (self._getObjPosition(o2) - Vector(c)).length)
952             )
953
954         # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
955         # then ob1 goes farther than obj2, useful when obj2 has holes
956         by_bbox = None
957         
958         Objects = scene.getChildren()
959         Objects.sort(by_center_pos)
960         
961         # update the scene
962         for o in Objects:
963             scene.unlink(o)
964             scene.link(o)
965
966     def _joinMeshObjectsInScene(self, scene):
967         """Merge all the Mesh Objects in a scene into a single Mesh Object.
968         """
969
970         oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
971
972         # FIXME: Object.join() do not work if the list contains 1 object
973         if len(oList) == 1:
974             return
975
976         mesh = Mesh.New('BigOne')
977         bigObj = Object.New('Mesh', 'BigOne')
978         bigObj.link(mesh)
979
980         scene.link(bigObj)
981
982         try:
983             bigObj.join(oList)
984         except RuntimeError:
985             print "\nCan't Join Objects\n"
986             scene.unlink(bigObj)
987             return
988         except TypeError:
989             print "Objects Type error?"
990         
991         for o in oList:
992             scene.unlink(o)
993
994         scene.update()
995
996  
997     # Per object methods
998
999     def _convertToRawMeshObj(self, object):
1000         """Convert geometry based object to a mesh object.
1001         """
1002         me = Mesh.New('RawMesh_'+object.name)
1003         me.getFromObject(object.name)
1004
1005         newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1006         newObject.link(me)
1007
1008         # If the object has no materials set a default material
1009         if not me.materials:
1010             me.materials = [Material.New()]
1011             #for f in me.faces: f.mat = 0
1012
1013         newObject.setMatrix(object.getMatrix())
1014
1015         return newObject
1016
1017     def _doModelingTransformation(self, mesh, matrix):
1018         """Transform object coordinates to world coordinates.
1019
1020         This step is done simply applying to the object its tranformation
1021         matrix and recalculating its normals.
1022         """
1023         # XXX FIXME: blender do not transform normals in the right way when
1024         # there are negative scale values
1025         if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1026             print "WARNING: Negative scales, expect incorrect results!"
1027
1028         mesh.transform(matrix, True)
1029
1030     def _doBackFaceCulling(self, mesh):
1031         """Simple Backface Culling routine.
1032         
1033         At this level we simply do a visibility test face by face and then
1034         select the vertices belonging to visible faces.
1035         """
1036         
1037         # Select all vertices, so edges can be displayed even if there are no
1038         # faces
1039         for v in mesh.verts:
1040             v.sel = 1
1041         
1042         Mesh.Mode(Mesh.SelectModes['FACE'])
1043         # Loop on faces
1044         for f in mesh.faces:
1045             f.sel = 0
1046             if self._isFaceVisible(f):
1047                 f.sel = 1
1048
1049     def _doPerVertexLighting(self, mesh):
1050         """Apply an Illumination ans shading model to the object.
1051
1052         The model used is the Phong one, it may be inefficient,
1053         but I'm just learning about rendering and starting from Phong seemed
1054         the most natural way.
1055         """
1056
1057         # If the mesh has vertex colors already, use them,
1058         # otherwise turn them on and do some calculations
1059         if mesh.vertexColors:
1060             return
1061         mesh.vertexColors = 1
1062
1063         materials = mesh.materials
1064         
1065         # TODO: use multiple lighting sources
1066         light_obj = self.lights[0]
1067         light_pos = self._getObjPosition(light_obj)
1068         light = light_obj.data
1069
1070         camPos = self._getObjPosition(self.cameraObj)
1071
1072         # We do per-face color calculation (FLAT Shading), we can easily turn
1073         # to a per-vertex calculation if we want to implement some shading
1074         # technique. For an example see:
1075         # http://www.miralab.unige.ch/papers/368.pdf
1076         for f in mesh.faces:
1077             if not f.sel:
1078                 continue
1079
1080             mat = None
1081             if materials:
1082                 mat = materials[f.mat]
1083
1084             # A new default material
1085             if mat == None:
1086                 mat = Material.New('defMat')
1087             
1088             L = Vector(light_pos).normalize()
1089
1090             V = (Vector(camPos) - Vector(f.cent)).normalize()
1091
1092             N = Vector(f.no).normalize()
1093
1094             R = 2 * (N*L) * N - L
1095
1096             # TODO: Attenuation factor (not used for now)
1097             a0 = 1.0; a1 = 0.0; a2 = 1.0
1098             d = (Vector(f.v[0].co) - Vector(light_pos)).length
1099             fd = min(1, 1.0/(a0 + a1*d + a2*(d*d)))
1100
1101             # Ambient component
1102             Ia = 1.0
1103             ka = mat.getAmb() * Vector([0.1, 0.1, 0.1])
1104             Iamb = Ia * ka
1105             
1106             # Diffuse component (add light.col for kd)
1107             kd = mat.getRef() * Vector(mat.getRGBCol())
1108             Ip = light.getEnergy()
1109             
1110             if config.polygons['SHADING'] == 'FLAT':
1111                 Idiff = Ip * kd * max(0, (N*L))
1112             elif config.polygons['SHADING'] == 'TOON':
1113                 Idiff = Ip * kd * MeshUtils.toonShading(N*L)
1114
1115             # Specular component
1116             ks = mat.getSpec() * Vector(mat.getSpecCol())
1117             ns = mat.getHardness()
1118             Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1119
1120             # Emissive component
1121             ki = Vector([mat.getEmit()]*3)
1122
1123             I = ki + Iamb + (Idiff + Ispec)
1124
1125
1126             # Set Alpha component
1127             I = list(I)
1128             I.append(mat.getAlpha())
1129
1130             # Clamp I values between 0 and 1
1131             I = [ min(c, 1) for c in I]
1132             I = [ max(0, c) for c in I]
1133
1134             # Convert to a value between 0 and 255
1135             tmp_col = [ int(c * 255.0) for c in I]
1136
1137             for c in f.col:
1138                 c.r = tmp_col[0]
1139                 c.g = tmp_col[1]
1140                 c.b = tmp_col[2]
1141                 c.a = tmp_col[3]
1142
1143     def _doProjection(self, mesh, projector):
1144         """Apply Viewing and Projection tranformations.
1145         """
1146
1147         for v in mesh.verts:
1148             p = projector.doProjection(v.co)
1149             v.co[0] = p[0]
1150             v.co[1] = p[1]
1151             v.co[2] = p[2]
1152
1153         # We could reeset Camera matrix, since now
1154         # we are in Normalized Viewing Coordinates,
1155         # but doung that would affect World Coordinate
1156         # processing for other objects
1157
1158         #self.cameraObj.data.type = 1
1159         #self.cameraObj.data.scale = 2.0
1160         #m = Matrix().identity()
1161         #self.cameraObj.setMatrix(m)
1162
1163     def _doViewFrustumClipping(self, mesh):
1164         """Clip faces against the View Frustum.
1165         """
1166
1167     def test_extensions(self, f1, f2):
1168         for v1, v2 in [ (v1, v2) for v1 in f1 for v2 in f2 ]:
1169             pass
1170
1171     def depth_sort(self, faces):
1172         return
1173             
1174
1175     def _doMeshDepthSorting(self, mesh):
1176         """Sort faces in an object.
1177
1178         The faces in the object are sorted following the distance of the
1179         vertices from the camera position.
1180         """
1181         if len(mesh.faces) == 0:
1182             return
1183
1184         #c = self._getObjPosition(self.cameraObj)
1185
1186         # In NVC
1187         c = [0, 0, 1]
1188
1189         # hackish sorting of faces
1190
1191         # Sort faces according to the max distance from the camera
1192         by_max_vert_dist = (lambda f1, f2:
1193                 cmp(max([(Vector(v.co)-Vector(c)).length for v in f2]),
1194                     max([(Vector(v.co)-Vector(c)).length for v in f1])))
1195         
1196         # Sort faces according to the min distance from the camera
1197         by_min_vert_dist = (lambda f1, f2:
1198                 cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
1199                     min([(Vector(v.co)-Vector(c)).length for v in f2])))
1200         
1201         # Sort faces according to the avg distance from the camera
1202         by_avg_vert_dist = (lambda f1, f2:
1203                 cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
1204                     sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
1205
1206
1207         # FIXME: using NMesh to sort faces. We should avoid that!
1208         nmesh = NMesh.GetRaw(mesh.name)
1209         nmesh.faces.sort(by_max_vert_dist)
1210         #nmesh.faces.reverse()
1211
1212         # Depth sort tests
1213
1214         self.depth_sort(nmesh.faces)
1215
1216         
1217         mesh.faces.delete(1, range(0, len(mesh.faces)))
1218
1219         for i,f in enumerate(nmesh.faces):
1220             fv = [v.index for v in f.v] 
1221             mesh.faces.extend(fv)
1222             mesh.faces[i].mat = f.mat
1223             mesh.faces[i].sel = f.sel
1224             for i,c in enumerate(mesh.faces[i].col):
1225                 c.r = f.col[i].r
1226                 c.g = f.col[i].g
1227                 c.b = f.col[i].b
1228                 c.a = f.col[i].a
1229
1230     def _doEdgesStyle(self, mesh, edgestyleSelect):
1231         """Process Mesh Edges accroding to a given selection style.
1232
1233         Examples of algorithms:
1234
1235         Contours:
1236             given an edge if its adjacent faces have the same normal (that is
1237             they are complanar), than deselect it.
1238
1239         Silhouettes:
1240             given an edge if one its adjacent faces is frontfacing and the
1241             other is backfacing, than select it, else deselect.
1242         """
1243
1244         Mesh.Mode(Mesh.SelectModes['EDGE'])
1245
1246         for e in mesh.edges:
1247
1248             e.sel = 0
1249             if edgestyleSelect(e, mesh):
1250                 e.sel = 1
1251                 
1252
1253
1254 # ---------------------------------------------------------------------
1255 #
1256 ## GUI Class and Main Program
1257 #
1258 # ---------------------------------------------------------------------
1259
1260
1261 from Blender import BGL, Draw
1262 from Blender.BGL import *
1263
1264 class GUI:
1265     
1266     def _init():
1267
1268         # Output Format menu 
1269         output_format = config.output['FORMAT']
1270         default_value = outputWriters.keys().index(output_format)+1
1271         GUI.outFormatMenu = Draw.Create(default_value)
1272         GUI.evtOutFormatMenu = 0
1273
1274         # Animation toggle button
1275         GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1276         GUI.evtAnimToggle = 1
1277
1278         # Join Objects toggle button
1279         GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1280         GUI.evtJoinObjsToggle = 2
1281
1282         # Render filled polygons
1283         GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1284
1285         # Shading Style menu 
1286         shading_style = config.polygons['SHADING']
1287         default_value = shadingStyles.keys().index(shading_style)+1
1288         GUI.shadingStyleMenu = Draw.Create(default_value)
1289         GUI.evtShadingStyleMenu = 21
1290
1291         GUI.evtPolygonsToggle = 3
1292         # We hide the config.polygons['EXPANSION_TRICK'], for now
1293
1294         # Render polygon edges
1295         GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1296         GUI.evtShowEdgesToggle = 4
1297
1298         # Render hidden edges
1299         GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1300         GUI.evtShowHiddenEdgesToggle = 5
1301
1302         # Edge Style menu 
1303         edge_style = config.edges['STYLE']
1304         default_value = edgeStyles.keys().index(edge_style)+1
1305         GUI.edgeStyleMenu = Draw.Create(default_value)
1306         GUI.evtEdgeStyleMenu = 6
1307
1308         # Edge Width slider
1309         GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
1310         GUI.evtEdgeWidthSlider = 7
1311
1312         # Edge Color Picker
1313         c = config.edges['COLOR']
1314         GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
1315         GUI.evtEdgeColorPicker = 71
1316
1317         # Render Button
1318         GUI.evtRenderButton = 8
1319
1320         # Exit Button
1321         GUI.evtExitButton = 9
1322
1323     def draw():
1324
1325         # initialize static members
1326         GUI._init()
1327
1328         glClear(GL_COLOR_BUFFER_BIT)
1329         glColor3f(0.0, 0.0, 0.0)
1330         glRasterPos2i(10, 350)
1331         Draw.Text("VRM: Vector Rendering Method script.")
1332         glRasterPos2i(10, 335)
1333         Draw.Text("Press Q or ESC to quit.")
1334
1335         # Build the output format menu
1336         glRasterPos2i(10, 310)
1337         Draw.Text("Select the output Format:")
1338         outMenuStruct = "Output Format %t"
1339         for t in outputWriters.keys():
1340            outMenuStruct = outMenuStruct + "|%s" % t
1341         GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1342                 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1343
1344         # Animation toggle
1345         GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1346                 10, 260, 160, 18, GUI.animToggle.val,
1347                 "Toggle rendering of animations")
1348
1349         # Join Objects toggle
1350         GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1351                 10, 235, 160, 18, GUI.joinObjsToggle.val,
1352                 "Join objects in the rendered file")
1353
1354         # Render Button
1355         Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1356                 "Start Rendering")
1357         Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1358
1359         # Rendering Styles
1360         glRasterPos2i(200, 310)
1361         Draw.Text("Rendering Style:")
1362
1363         # Render Polygons
1364         GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1365                 200, 285, 160, 18, GUI.polygonsToggle.val,
1366                 "Render filled polygons")
1367
1368         if GUI.polygonsToggle.val == 1:
1369
1370             # Polygon Shading Style
1371             shadingStyleMenuStruct = "Shading Style %t"
1372             for t in shadingStyles.keys():
1373                 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
1374             GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
1375                     200, 260, 160, 18, GUI.shadingStyleMenu.val,
1376                     "Choose the shading style")
1377
1378
1379         # Render Edges
1380         GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1381                 200, 235, 160, 18, GUI.showEdgesToggle.val,
1382                 "Render polygon edges")
1383
1384         if GUI.showEdgesToggle.val == 1:
1385             
1386             # Edge Style
1387             edgeStyleMenuStruct = "Edge Style %t"
1388             for t in edgeStyles.keys():
1389                 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
1390             GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1391                     200, 210, 160, 18, GUI.edgeStyleMenu.val,
1392                     "Choose the edge style")
1393
1394             # Edge size
1395             GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1396                     200, 185, 140, 18, GUI.edgeWidthSlider.val,
1397                     0.0, 10.0, 0, "Change Edge Width")
1398
1399             # Edge Color
1400             GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
1401                     342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
1402
1403             # Show Hidden Edges
1404             GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1405                     GUI.evtShowHiddenEdgesToggle,
1406                     200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
1407                     "Render hidden edges as dashed lines")
1408
1409         glRasterPos2i(10, 160)
1410         Draw.Text("Antonio Ospite (c) 2006")
1411
1412     def event(evt, val):
1413
1414         if evt == Draw.ESCKEY or evt == Draw.QKEY:
1415             Draw.Exit()
1416         else:
1417             return
1418
1419         Draw.Redraw(1)
1420
1421     def button_event(evt):
1422
1423         if evt == GUI.evtExitButton:
1424             Draw.Exit()
1425
1426         elif evt == GUI.evtOutFormatMenu:
1427             i = GUI.outFormatMenu.val - 1
1428             config.output['FORMAT']= outputWriters.keys()[i]
1429
1430         elif evt == GUI.evtAnimToggle:
1431             config.outpur['ANIMATION'] = bool(GUI.animToggle.val)
1432
1433         elif evt == GUI.evtJoinObjsToggle:
1434             config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
1435
1436         elif evt == GUI.evtPolygonsToggle:
1437             config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
1438
1439         elif evt == GUI.evtShadingStyleMenu:
1440             i = GUI.shadingStyleMenu.val - 1
1441             config.polygons['SHADING'] = shadingStyles.keys()[i]
1442
1443         elif evt == GUI.evtShowEdgesToggle:
1444             config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
1445
1446         elif evt == GUI.evtShowHiddenEdgesToggle:
1447             config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
1448
1449         elif evt == GUI.evtEdgeStyleMenu:
1450             i = GUI.edgeStyleMenu.val - 1
1451             config.edges['STYLE'] = edgeStyles.keys()[i]
1452
1453         elif evt == GUI.evtEdgeWidthSlider:
1454             config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
1455
1456         elif evt == GUI.evtEdgeColorPicker:
1457             config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
1458
1459         elif evt == GUI.evtRenderButton:
1460             label = "Save %s" % config.output['FORMAT']
1461             # Show the File Selector
1462             global outputfile
1463             Blender.Window.FileSelector(vectorize, label, outputfile)
1464
1465         else:
1466             print "Event: %d not handled!" % evt
1467
1468         if evt:
1469             Draw.Redraw(1)
1470             #GUI.conf_debug()
1471
1472     def conf_debug():
1473         from pprint import pprint
1474         print "\nConfig"
1475         pprint(config.output)
1476         pprint(config.polygons)
1477         pprint(config.edges)
1478
1479     _init = staticmethod(_init)
1480     draw = staticmethod(draw)
1481     event = staticmethod(event)
1482     button_event = staticmethod(button_event)
1483     conf_debug = staticmethod(conf_debug)
1484
1485 # A wrapper function for the vectorizing process
1486 def vectorize(filename):
1487     """The vectorizing process is as follows:
1488      
1489      - Instanciate the writer and the renderer
1490      - Render!
1491      """
1492
1493     if filename == "":
1494         print "\nERROR: invalid file name!"
1495         return
1496
1497     from Blender import Window
1498     editmode = Window.EditMode()
1499     if editmode: Window.EditMode(0)
1500
1501     actualWriter = outputWriters[config.output['FORMAT']]
1502     writer = actualWriter(filename)
1503     
1504     renderer = Renderer()
1505     renderer.doRendering(writer, config.output['ANIMATION'])
1506
1507     if editmode: Window.EditMode(1) 
1508
1509
1510 # Here the main
1511 if __name__ == "__main__":
1512     
1513     outputfile = ""
1514     basename = Blender.sys.basename(Blender.Get('filename'))
1515     if basename != "":
1516         outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
1517
1518     if Blender.mode == 'background':
1519         vectorize(outputfile)
1520     else:
1521         Draw.Register(GUI.draw, GUI.event, GUI.button_event)