6 Tooltip: 'Vector Rendering Method script'
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://projects.blender.org/projects/vrm"]
11 __version__ = "0.3.beta"
14 Render the scene and save the result in vector format.
17 # ---------------------------------------------------------------------
18 # Copyright (c) 2006 Antonio Ospite
20 # This program is free software; you can redistribute it and/or modify
21 # it under the terms of the GNU General Public License as published by
22 # the Free Software Foundation; either version 2 of the License, or
23 # (at your option) any later version.
25 # This program is distributed in the hope that it will be useful,
26 # but WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 # GNU General Public License for more details.
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
34 # ---------------------------------------------------------------------
37 # Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
38 # Thanks to Nikola Radovanovic, the author of the original VRM script,
39 # the code you read here has been rewritten _almost_ entirely
40 # from scratch but Nikola gave me the idea, so I thank him publicly.
42 # ---------------------------------------------------------------------
44 # Things TODO for a next release:
45 # - FIX the issue with negative scales in object tranformations!
46 # - Use a better depth sorting algorithm
47 # - Implement clipping of primitives and do handle object intersections.
48 # (for now only clipping away whole objects is supported).
49 # - Review how selections are made (this script uses selection states of
50 # primitives to represent visibility infos)
51 # - Use a data structure other than Mesh to represent the 2D image?
52 # Think to a way to merge (adjacent) polygons that have the same color.
53 # Or a way to use paths for silhouettes and contours.
54 # - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
55 # not support SMIL for animations)
56 # - Switch to the Mesh structure, should be considerably faster
57 # (partially done, but with Mesh we cannot sort faces, yet)
58 # - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
59 # - Implement Shading Styles? (partially done, to make more flexible).
60 # - Add Vector Writers other than SVG.
61 # - Check memory use!!
62 # - Support Indexed palettes!! (Useful for ILDA FILES, for example,
63 # see http://www.linux-laser.org/download/autotrace/ilda-output.patch)
65 # ---------------------------------------------------------------------
70 # * First release after code restucturing.
71 # Now the script offers a useful set of functionalities
72 # and it can render animations, too.
73 # * Optimization in Renderer.doEdgeStyle(), build a topology cache
74 # so to speed up the lookup of adjacent faces of an edge.
76 # * The SVG output is now SVG 1.0 valid.
77 # Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
78 # * Progress indicator during HSR.
80 # ---------------------------------------------------------------------
83 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
84 from Blender.Mathutils import *
89 # Some global settings
93 polygons['SHOW'] = True
94 polygons['SHADING'] = 'FLAT'
95 polygons['HSR'] = 'PAINTER' # 'PAINTER' or 'NEWELL'
96 #polygons['HSR'] = 'NEWELL'
97 # Hidden to the user for now
98 polygons['EXPANSION_TRICK'] = True
100 polygons['TOON_LEVELS'] = 2
103 edges['SHOW'] = False
104 edges['SHOW_HIDDEN'] = False
105 edges['STYLE'] = 'MESH'
107 edges['COLOR'] = [0, 0, 0]
110 output['FORMAT'] = 'SVG'
111 output['ANIMATION'] = False
112 output['JOIN_OBJECTS'] = True
116 # Debug utility function
120 sys.stderr.write(msg)
123 # ---------------------------------------------------------------------
125 ## Mesh Utility class
127 # ---------------------------------------------------------------------
130 def buildEdgeFaceUsersCache(me):
132 Takes a mesh and returns a list aligned with the meshes edges.
133 Each item is a list of the faces that use the edge
134 would be the equiv for having ed.face_users as a property
136 Taken from .blender/scripts/bpymodules/BPyMesh.py,
137 thanks to ideasman_42.
140 def sorted_edge_indicies(ed):
148 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
150 fvi= [v.index for v in f.v]# face vert idx's
151 for i in xrange(len(f)):
158 face_edges_dict[i1,i2][1].append(f)
160 face_edges= [None] * len(me.edges)
161 for ed_index, ed_faces in face_edges_dict.itervalues():
162 face_edges[ed_index]= ed_faces
166 def isMeshEdge(adjacent_faces):
169 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
170 Note: if the edge has no adjacent faces we want to show it as well,
171 useful for "edge only" portion of objects.
174 if len(adjacent_faces) == 0:
177 selected_faces = [f for f in adjacent_faces if f.sel]
179 if len(selected_faces) != 0:
184 def isSilhouetteEdge(adjacent_faces):
185 """Silhuette selection rule.
187 An edge is a silhuette edge if it is shared by two faces with
188 different selection status or if it is a boundary edge of a selected
192 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
193 (len(adjacent_faces) == 2 and
194 adjacent_faces[0].sel != adjacent_faces[1].sel)
200 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
201 isMeshEdge = staticmethod(isMeshEdge)
202 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
205 # ---------------------------------------------------------------------
207 ## Shading Utility class
209 # ---------------------------------------------------------------------
214 def toonShadingMapSetup():
215 levels = config.polygons['TOON_LEVELS']
217 texels = 2*levels - 1
218 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
224 shademap = ShadingUtils.shademap
227 shademap = ShadingUtils.toonShadingMapSetup()
230 for i in xrange(0, len(shademap)-1):
231 pivot = (shademap[i]+shademap[i+1])/2.0
236 if v < shademap[i+1]:
241 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
242 toonShading = staticmethod(toonShading)
245 # ---------------------------------------------------------------------
247 ## Projections classes
249 # ---------------------------------------------------------------------
252 """Calculate the projection of an object given the camera.
254 A projector is useful to so some per-object transformation to obtain the
255 projection of an object given the camera.
257 The main method is #doProjection# see the method description for the
261 def __init__(self, cameraObj, canvasRatio):
262 """Calculate the projection matrix.
264 The projection matrix depends, in this case, on the camera settings.
265 TAKE CARE: This projector expects vertices in World Coordinates!
268 camera = cameraObj.getData()
270 aspect = float(canvasRatio[0])/float(canvasRatio[1])
271 near = camera.clipStart
274 scale = float(camera.scale)
276 fovy = atan(0.5/aspect/(camera.lens/32))
277 fovy = fovy * 360.0/pi
279 # What projection do we want?
281 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
282 elif camera.type == 1:
283 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
285 # View transformation
286 cam = Matrix(cameraObj.getInverseMatrix())
291 self.projectionMatrix = mP
297 def doProjection(self, v):
298 """Project the point on the view plane.
300 Given a vertex calculate the projection using the current projection
304 # Note that we have to work on the vertex using homogeneous coordinates
305 # From blender 2.42+ we don't need to resize the vector to be 4d
306 # when applying a 4x4 matrix, but we do that anyway since we need the
307 # 4th coordinate later
308 p = self.projectionMatrix * Vector(v).resize4D()
310 # Perspective division
327 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
328 """Return a perspective projection matrix.
331 top = near * tan(fovy * pi / 360.0)
335 x = (2.0 * near) / (right-left)
336 y = (2.0 * near) / (top-bottom)
337 a = (right+left) / (right-left)
338 b = (top+bottom) / (top - bottom)
339 c = - ((far+near) / (far-near))
340 d = - ((2*far*near)/(far-near))
346 [0.0, 0.0, -1.0, 0.0])
350 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
351 """Return an orthogonal projection matrix.
354 # The 11 in the formula was found emiprically
355 top = near * tan(fovy * pi / 360.0) * (scale * 11)
357 left = bottom * aspect
362 tx = -((right+left)/rl)
363 ty = -((top+bottom)/tb)
367 [2.0/rl, 0.0, 0.0, tx],
368 [0.0, 2.0/tb, 0.0, ty],
369 [0.0, 0.0, 2.0/fn, tz],
370 [0.0, 0.0, 0.0, 1.0])
375 # ---------------------------------------------------------------------
377 ## Progress Indicator
379 # ---------------------------------------------------------------------
382 """A model for a progress indicator.
384 Do the progress calculation calculation and
385 the view independent stuff of a progress indicator.
387 def __init__(self, steps=0):
393 def setSteps(self, steps):
394 """Set the number of steps of the activity wich we want to track.
401 def setName(self, name):
402 """Set the name of the activity wich we want to track.
409 def getProgress(self):
417 """Update the model, call this method when one step is completed.
419 if self.progress == 100:
423 self.progress = ( float(self.completed) / float(self.steps) ) * 100
424 self.progress = int(self.progress)
429 class ProgressIndicator:
430 """An abstraction of a View for the Progress Model
434 # Use a refresh rate so we do not show the progress at
435 # every update, but every 'self.refresh_rate' times.
436 self.refresh_rate = 10
437 self.shows_counter = 0
439 self.progressModel = None
441 def setActivity(self, name, steps):
442 """Initialize the Model.
444 In a future version (with subactivities-progress support) this method
445 could only set the current activity.
447 self.progressModel = Progress()
448 self.progressModel.setName(name)
449 self.progressModel.setSteps(steps)
451 def getActivity(self):
452 return self.progressModel
455 """Update the model and show the actual progress.
457 assert(self.progressModel)
459 if self.progressModel.update():
460 self.show(self.progressModel.getProgress(),
461 self.progressModel.getName())
463 # We return always True here so we can call the update() method also
464 # from lambda funcs (putting the call in logical AND with other ops)
467 def show(self, progress, name=""):
468 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
469 if self.shows_counter != 0:
473 self.shows_counter = -1
476 class ConsoleProgressIndicator(ProgressIndicator):
477 """Show a progress bar on stderr, a la wget.
480 ProgressIndicator.__init__(self)
482 self.swirl_chars = ["-", "\\", "|", "/"]
483 self.swirl_count = -1
485 def show(self, progress, name):
486 ProgressIndicator.show(self, progress, name)
489 bar_progress = int( (progress/100.0) * bar_length )
490 bar = ("=" * bar_progress).ljust(bar_length)
492 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
493 swirl_char = self.swirl_chars[self.swirl_count]
495 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
497 sys.stderr.write(progress_bar+"\r")
499 sys.stderr.write("\n")
502 class GraphicalProgressIndicator(ProgressIndicator):
503 """Interface to the Blender.Window.DrawProgressBar() method.
506 ProgressIndicator.__init__(self)
508 #self.swirl_chars = ["-", "\\", "|", "/"]
509 # We have to use letters with the same width, for now!
510 # Blender progress bar considers the font widths when
511 # calculating the progress bar width.
512 self.swirl_chars = ["\\", "/"]
513 self.swirl_count = -1
515 def show(self, progress, name):
516 ProgressIndicator.show(self, progress)
518 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
519 swirl_char = self.swirl_chars[self.swirl_count]
521 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
523 # Finally draw the Progress Bar
524 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
525 Window.DrawProgressBar(progress/100.0, progress_text)
528 Window.DrawProgressBar(1, progress_text)
533 # ---------------------------------------------------------------------
535 ## 2D Object representation class
537 # ---------------------------------------------------------------------
539 # TODO: a class to represent the needed properties of a 2D vector image
540 # For now just using a [N]Mesh structure.
543 # ---------------------------------------------------------------------
545 ## Vector Drawing Classes
547 # ---------------------------------------------------------------------
553 A class for printing output in a vectorial format.
555 Given a 2D representation of the 3D scene the class is responsible to
556 write it is a vector format.
558 Every subclasses of VectorWriter must have at last the following public
562 - printCanvas(self, scene,
563 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
566 def __init__(self, fileName):
567 """Set the output file name and other properties"""
569 self.outputFileName = fileName
572 context = Scene.GetCurrent().getRenderingContext()
573 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
577 self.animation = False
584 def open(self, startFrame=1, endFrame=1):
585 if startFrame != endFrame:
586 self.startFrame = startFrame
587 self.endFrame = endFrame
588 self.animation = True
590 self.file = open(self.outputFileName, "w")
591 print "Outputting to: ", self.outputFileName
599 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
600 showHiddenEdges=False):
601 """This is the interface for the needed printing routine.
608 class SVGVectorWriter(VectorWriter):
609 """A concrete class for writing SVG output.
612 def __init__(self, fileName):
613 """Simply call the parent Contructor.
615 VectorWriter.__init__(self, fileName)
622 def open(self, startFrame=1, endFrame=1):
623 """Do some initialization operations.
625 VectorWriter.open(self, startFrame, endFrame)
629 """Do some finalization operation.
633 # remember to call the close method of the parent
634 VectorWriter.close(self)
637 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
638 showHiddenEdges=False):
639 """Convert the scene representation to SVG.
642 Objects = scene.getChildren()
644 context = scene.getRenderingContext()
645 framenumber = context.currentFrame()
648 framestyle = "display:none"
650 framestyle = "display:block"
652 # Assign an id to this group so we can set properties on it using DOM
653 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
654 (framenumber, framestyle) )
659 if(obj.getType() != 'Mesh'):
662 self.file.write("<g id=\"%s\">\n" % obj.getName())
664 mesh = obj.getData(mesh=1)
667 self._printPolygons(mesh)
670 self._printEdges(mesh, showHiddenEdges)
672 self.file.write("</g>\n")
674 self.file.write("</g>\n")
681 def _calcCanvasCoord(self, v):
682 """Convert vertex in scene coordinates to canvas coordinates.
685 pt = Vector([0, 0, 0])
687 mW = float(self.canvasSize[0])/2.0
688 mH = float(self.canvasSize[1])/2.0
690 # rescale to canvas size
691 pt[0] = v.co[0]*mW + mW
692 pt[1] = v.co[1]*mH + mH
695 # For now we want (0,0) in the top-left corner of the canvas.
696 # Mirror and translate along y
698 pt[1] += self.canvasSize[1]
702 def _printHeader(self):
703 """Print SVG header."""
705 self.file.write("<?xml version=\"1.0\"?>\n")
706 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
707 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
708 self.file.write("<svg version=\"1.0\"\n")
709 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
710 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
715 self.file.write("""\n<script type="text/javascript"><![CDATA[
719 /* FIXME: Use 1000 as interval as lower values gives problems */
720 timerID = setInterval("NextFrame()", 1000);
721 globalFrameCounter=%d;
725 currentElement = document.getElementById('frame'+globalFrameCounter)
726 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
733 if (globalFrameCounter > globalEndFrame)
735 clearInterval(timerID)
741 previousElement.style.display="none";
743 currentElement.style.display="block";
744 globalFrameCounter++;
748 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
750 def _printFooter(self):
751 """Print the SVG footer."""
753 self.file.write("\n</svg>\n")
755 def _printPolygons(self, mesh):
756 """Print the selected (visible) polygons.
759 if len(mesh.faces) == 0:
762 self.file.write("<g>\n")
764 for face in mesh.faces:
768 self.file.write("<path d=\"")
770 p = self._calcCanvasCoord(face.verts[0])
771 self.file.write("M %g,%g L " % (p[0], p[1]))
773 for v in face.verts[1:]:
774 p = self._calcCanvasCoord(v)
775 self.file.write("%g,%g " % (p[0], p[1]))
777 # get rid of the last blank space, just cosmetics here.
778 self.file.seek(-1, 1)
779 self.file.write(" z\"\n")
781 # take as face color the first vertex color
784 color = [fcol.r, fcol.g, fcol.b, fcol.a]
786 color = [255, 255, 255, 255]
788 # Convert the color to the #RRGGBB form
789 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
791 # Handle transparent polygons
794 opacity = float(color[3])/255.0
795 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
797 self.file.write("\tstyle=\"fill:" + str_col + ";")
798 self.file.write(opacity_string)
800 # use the stroke property to alleviate the "adjacent edges" problem,
801 # we simulate polygon expansion using borders,
802 # see http://www.antigrain.com/svg/index.html for more info
805 if config.polygons['EXPANSION_TRICK']:
806 self.file.write(" stroke:%s;\n" % str_col)
807 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
808 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
810 self.file.write("\"/>\n")
812 self.file.write("</g>\n")
814 def _printEdges(self, mesh, showHiddenEdges=False):
815 """Print the wireframe using mesh edges.
818 stroke_width = config.edges['WIDTH']
819 stroke_col = config.edges['COLOR']
821 self.file.write("<g>\n")
825 hidden_stroke_style = ""
828 if showHiddenEdges == False:
831 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
833 p1 = self._calcCanvasCoord(e.v1)
834 p2 = self._calcCanvasCoord(e.v2)
836 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
837 % ( p1[0], p1[1], p2[0], p2[1] ) )
838 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
839 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
840 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
841 self.file.write(hidden_stroke_style)
842 self.file.write("\"/>\n")
844 self.file.write("</g>\n")
847 # ---------------------------------------------------------------------
851 # ---------------------------------------------------------------------
853 # A dictionary to collect different shading style methods
854 shadingStyles = dict()
855 shadingStyles['FLAT'] = None
856 shadingStyles['TOON'] = None
858 # A dictionary to collect different edge style methods
860 edgeStyles['MESH'] = MeshUtils.isMeshEdge
861 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
863 # A dictionary to collect the supported output formats
864 outputWriters = dict()
865 outputWriters['SVG'] = SVGVectorWriter
869 """Render a scene viewed from the active camera.
871 This class is responsible of the rendering process, transformation and
872 projection of the objects in the scene are invoked by the renderer.
874 The rendering is done using the active camera for the current scene.
878 """Make the rendering process only for the current scene by default.
880 We will work on a copy of the scene, to be sure that the current scene do
881 not get modified in any way.
884 # Render the current Scene, this should be a READ-ONLY property
885 self._SCENE = Scene.GetCurrent()
887 # Use the aspect ratio of the scene rendering context
888 context = self._SCENE.getRenderingContext()
890 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
891 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
892 float(context.aspectRatioY())
895 # Render from the currently active camera
896 self.cameraObj = self._SCENE.getCurrentCamera()
898 # Get a projector for this camera.
899 # NOTE: the projector wants object in world coordinates,
900 # so we should remember to apply modelview transformations
901 # _before_ we do projection transformations.
902 self.proj = Projector(self.cameraObj, self.canvasRatio)
904 # Get the list of lighting sources
905 obj_lst = self._SCENE.getChildren()
906 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
908 # When there are no lights we use a default lighting source
909 # that have the same position of the camera
910 if len(self.lights) == 0:
912 lobj = Object.New('Lamp')
913 lobj.loc = self.cameraObj.loc
915 self.lights.append(lobj)
922 def doRendering(self, outputWriter, animation=False):
923 """Render picture or animation and write it out.
926 - a Vector writer object that will be used to output the result.
927 - a flag to tell if we want to render an animation or only the
931 context = self._SCENE.getRenderingContext()
932 origCurrentFrame = context.currentFrame()
934 # Handle the animation case
936 startFrame = origCurrentFrame
937 endFrame = startFrame
940 startFrame = context.startFrame()
941 endFrame = context.endFrame()
942 outputWriter.open(startFrame, endFrame)
944 # Do the rendering process frame by frame
945 print "Start Rendering of %d frames" % (endFrame-startFrame)
946 for f in xrange(startFrame, endFrame+1):
947 print "\n\nFrame: %d" % f
948 context.currentFrame(f)
950 # Use some temporary workspace, a full copy of the scene
951 inputScene = self._SCENE.copy(2)
952 # And Set our camera accordingly
953 self.cameraObj = inputScene.getCurrentCamera()
956 renderedScene = self.doRenderScene(inputScene)
958 print "There was an error! Aborting."
960 print traceback.print_exc()
962 self._SCENE.makeCurrent()
963 Scene.unlink(inputScene)
967 outputWriter.printCanvas(renderedScene,
968 doPrintPolygons = config.polygons['SHOW'],
969 doPrintEdges = config.edges['SHOW'],
970 showHiddenEdges = config.edges['SHOW_HIDDEN'])
972 # delete the rendered scene
973 self._SCENE.makeCurrent()
974 Scene.unlink(renderedScene)
979 context.currentFrame(origCurrentFrame)
982 def doRenderScene(self, workScene):
983 """Control the rendering process.
985 Here we control the entire rendering process invoking the operation
986 needed to transform and project the 3D scene in two dimensions.
989 # global processing of the scene
991 self._doSceneClipping(workScene)
993 self._doConvertGeometricObjsToMesh(workScene)
995 if config.output['JOIN_OBJECTS']:
996 self._joinMeshObjectsInScene(workScene)
998 self._doSceneDepthSorting(workScene)
1000 # Per object activities
1002 Objects = workScene.getChildren()
1003 print "Total Objects: %d" % len(Objects)
1004 for i,obj in enumerate(Objects):
1005 print "Rendering Object: %d" % i
1007 if obj.getType() != 'Mesh':
1008 print "Only Mesh supported! - Skipping type:", obj.getType()
1011 print "Rendering: ", obj.getName()
1013 mesh = obj.getData(mesh=1)
1015 self._doModelingTransformation(mesh, obj.matrix)
1017 self._doBackFaceCulling(mesh)
1019 self._doLighting(mesh)
1021 # Do "projection" now so we perform further processing
1022 # in Normalized View Coordinates
1023 self._doProjection(mesh, self.proj)
1025 self._doViewFrustumClipping(mesh)
1027 self._doHiddenSurfaceRemoval(mesh)
1029 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
1032 # Update the object data, important! :)
1044 def _getObjPosition(self, obj):
1045 """Return the obj position in World coordinates.
1047 return obj.matrix.translationPart()
1049 def _cameraViewVector(self):
1050 """Get the View Direction form the camera matrix.
1052 return Vector(self.cameraObj.matrix[2]).resize3D()
1057 def _isFaceVisible(self, face):
1058 """Determine if a face of an object is visible from the current camera.
1060 The view vector is calculated from the camera location and one of the
1061 vertices of the face (expressed in World coordinates, after applying
1062 modelview transformations).
1064 After those transformations we determine if a face is visible by
1065 computing the angle between the face normal and the view vector, this
1066 angle has to be between -90 and 90 degrees for the face to be visible.
1067 This corresponds somehow to the dot product between the two, if it
1068 results > 0 then the face is visible.
1070 There is no need to normalize those vectors since we are only interested in
1071 the sign of the cross product and not in the product value.
1073 NOTE: here we assume the face vertices are in WorldCoordinates, so
1074 please transform the object _before_ doing the test.
1077 normal = Vector(face.no)
1078 camPos = self._getObjPosition(self.cameraObj)
1081 # View Vector in orthographics projections is the view Direction of
1083 if self.cameraObj.data.getType() == 1:
1084 view_vect = self._cameraViewVector()
1086 # View vector in perspective projections can be considered as
1087 # the difference between the camera position and one point of
1088 # the face, we choose the farthest point from the camera.
1089 if self.cameraObj.data.getType() == 0:
1090 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
1094 # if d > 0 the face is visible from the camera
1095 d = view_vect * normal
1105 def _doSceneClipping(self, scene):
1106 """Clip whole objects against the View Frustum.
1108 For now clip away only objects according to their center position.
1111 cpos = self._getObjPosition(self.cameraObj)
1112 view_vect = self._cameraViewVector()
1114 near = self.cameraObj.data.clipStart
1115 far = self.cameraObj.data.clipEnd
1117 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
1118 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
1119 fovy = fovy * 360.0/pi
1121 Objects = scene.getChildren()
1123 if o.getType() != 'Mesh': continue;
1125 obj_vect = Vector(cpos) - self._getObjPosition(o)
1127 d = obj_vect*view_vect
1128 theta = AngleBetweenVecs(obj_vect, view_vect)
1130 # if the object is outside the view frustum, clip it away
1131 if (d < near) or (d > far) or (theta > fovy):
1134 def _doConvertGeometricObjsToMesh(self, scene):
1135 """Convert all "geometric" objects to mesh ones.
1137 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
1139 Objects = scene.getChildren()
1140 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
1143 obj = self._convertToRawMeshObj(obj)
1145 scene.unlink(old_obj)
1148 # XXX Workaround for Text and Curve which have some normals
1149 # inverted when they are converted to Mesh, REMOVE that when
1150 # blender will fix that!!
1151 if old_obj.getType() in ['Curve', 'Text']:
1152 me = obj.getData(mesh=1)
1153 for f in me.faces: f.sel = 1;
1154 for v in me.verts: v.sel = 1;
1161 def _doSceneDepthSorting(self, scene):
1162 """Sort objects in the scene.
1164 The object sorting is done accordingly to the object centers.
1167 c = self._getObjPosition(self.cameraObj)
1169 by_center_pos = (lambda o1, o2:
1170 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
1171 cmp((self._getObjPosition(o1) - Vector(c)).length,
1172 (self._getObjPosition(o2) - Vector(c)).length)
1175 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
1176 # then ob1 goes farther than obj2, useful when obj2 has holes
1179 Objects = scene.getChildren()
1180 Objects.sort(by_center_pos)
1187 def _joinMeshObjectsInScene(self, scene):
1188 """Merge all the Mesh Objects in a scene into a single Mesh Object.
1191 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
1193 # FIXME: Object.join() do not work if the list contains 1 object
1197 mesh = Mesh.New('BigOne')
1198 bigObj = Object.New('Mesh', 'BigOne')
1205 except RuntimeError:
1206 print "\nWarning! - Can't Join Objects\n"
1207 scene.unlink(bigObj)
1210 print "Objects Type error?"
1218 # Per object/mesh methods
1220 def _convertToRawMeshObj(self, object):
1221 """Convert geometry based object to a mesh object.
1223 me = Mesh.New('RawMesh_'+object.name)
1224 me.getFromObject(object.name)
1226 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1229 # If the object has no materials set a default material
1230 if not me.materials:
1231 me.materials = [Material.New()]
1232 #for f in me.faces: f.mat = 0
1234 newObject.setMatrix(object.getMatrix())
1238 def _doModelingTransformation(self, mesh, matrix):
1239 """Transform object coordinates to world coordinates.
1241 This step is done simply applying to the object its tranformation
1242 matrix and recalculating its normals.
1244 # XXX FIXME: blender do not transform normals in the right way when
1245 # there are negative scale values
1246 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1247 print "WARNING: Negative scales, expect incorrect results!"
1249 mesh.transform(matrix, True)
1251 def _doBackFaceCulling(self, mesh):
1252 """Simple Backface Culling routine.
1254 At this level we simply do a visibility test face by face and then
1255 select the vertices belonging to visible faces.
1258 # Select all vertices, so edges can be displayed even if there are no
1260 for v in mesh.verts:
1263 Mesh.Mode(Mesh.SelectModes['FACE'])
1265 for f in mesh.faces:
1267 if self._isFaceVisible(f):
1270 def _doLighting(self, mesh):
1271 """Apply an Illumination and shading model to the object.
1273 The model used is the Phong one, it may be inefficient,
1274 but I'm just learning about rendering and starting from Phong seemed
1275 the most natural way.
1278 # If the mesh has vertex colors already, use them,
1279 # otherwise turn them on and do some calculations
1280 if mesh.vertexColors:
1282 mesh.vertexColors = 1
1284 materials = mesh.materials
1286 camPos = self._getObjPosition(self.cameraObj)
1288 # We do per-face color calculation (FLAT Shading), we can easily turn
1289 # to a per-vertex calculation if we want to implement some shading
1290 # technique. For an example see:
1291 # http://www.miralab.unige.ch/papers/368.pdf
1292 for f in mesh.faces:
1298 mat = materials[f.mat]
1299 # Check if it is a shadeless material
1300 if mat.getMode() & Material.Modes['SHADELESS']:
1302 # Convert to a value between 0 and 255
1303 tmp_col = [ int(c * 255.0) for c in I]
1315 # A new default material
1317 mat = Material.New('defMat')
1319 # do vertex color calculation
1321 TotDiffSpec = Vector([0.0, 0.0, 0.0])
1323 for l in self.lights:
1325 light_pos = self._getObjPosition(l)
1326 light = light_obj.data
1328 L = Vector(light_pos).normalize()
1330 V = (Vector(camPos) - Vector(f.cent)).normalize()
1332 N = Vector(f.no).normalize()
1334 if config.polygons['SHADING'] == 'TOON':
1335 NL = ShadingUtils.toonShading(N*L)
1339 # Should we use NL instead of (N*L) here?
1340 R = 2 * (N*L) * N - L
1342 Ip = light.getEnergy()
1344 # Diffuse co-efficient
1345 kd = mat.getRef() * Vector(mat.getRGBCol())
1347 kd[i] *= light.col[i]
1349 Idiff = Ip * kd * max(0, NL)
1352 # Specular component
1353 ks = mat.getSpec() * Vector(mat.getSpecCol())
1354 ns = mat.getHardness()
1355 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1357 TotDiffSpec += (Idiff+Ispec)
1361 Iamb = Vector(Blender.World.Get()[0].getAmb())
1364 # Emissive component (convert to a triplet)
1365 ki = Vector([mat.getEmit()]*3)
1367 #I = ki + Iamb + (Idiff + Ispec)
1368 I = ki + (ka * Iamb) + TotDiffSpec
1371 # Set Alpha component
1373 I.append(mat.getAlpha())
1375 # Clamp I values between 0 and 1
1376 I = [ min(c, 1) for c in I]
1377 I = [ max(0, c) for c in I]
1379 # Convert to a value between 0 and 255
1380 tmp_col = [ int(c * 255.0) for c in I]
1388 def _doProjection(self, mesh, projector):
1389 """Apply Viewing and Projection tranformations.
1392 for v in mesh.verts:
1393 p = projector.doProjection(v.co[:])
1398 #mesh.recalcNormals()
1401 # We could reeset Camera matrix, since now
1402 # we are in Normalized Viewing Coordinates,
1403 # but doung that would affect World Coordinate
1404 # processing for other objects
1406 #self.cameraObj.data.type = 1
1407 #self.cameraObj.data.scale = 2.0
1408 #m = Matrix().identity()
1409 #self.cameraObj.setMatrix(m)
1411 def _doViewFrustumClipping(self, mesh):
1412 """Clip faces against the View Frustum.
1416 def __simpleDepthSort(self, mesh):
1417 """Sort faces by the furthest vertex.
1419 This simple mesthod is known also as the painter algorithm, and it
1420 solves HSR correctly only for convex meshes.
1424 # The sorting requires circa n*log(n) steps
1426 progress.setActivity("HSR: Painter", n*log(n))
1429 by_furthest_z = (lambda f1, f2: progress.update() and
1430 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1433 # FIXME: using NMesh to sort faces. We should avoid that!
1434 nmesh = NMesh.GetRaw(mesh.name)
1436 # remember that _higher_ z values mean further points
1437 nmesh.faces.sort(by_furthest_z)
1438 nmesh.faces.reverse()
1442 def __topologicalDepthSort(self, mesh):
1443 """Occlusion based on topological occlusion.
1445 Build the occlusion graph of the mesh,
1446 and then do topological sort on that graph
1450 def __newellDepthSort(self, mesh):
1451 """Newell's depth sorting.
1454 by_furthest_z = (lambda f1, f2:
1455 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1458 def Distance(point, face):
1459 """ Calculate the distance between a point and a face.
1461 An alternative but more expensive method can be:
1463 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
1464 Vector(face.no), Vector(point), 0)
1466 d = Vector(ip - point).length
1469 plNormal = Vector(face.no)
1470 plVert0 = Vector(face[0])
1472 #d = abs( (point * plNormal ) - (plVert0 * plNormal) )
1473 d = (point * plNormal ) - (plVert0 * plNormal)
1474 debug("d: "+ str(d) + "\n")
1479 # FIXME: using NMesh to sort faces. We should avoid that!
1480 nmesh = NMesh.GetRaw(mesh.name)
1482 # remember that _higher_ z values mean further points
1483 nmesh.faces.sort(by_furthest_z)
1484 nmesh.faces.reverse()
1487 # Begin depth sort tests
1489 # use the smooth flag to set marked faces
1490 for f in nmesh.faces:
1493 facelist = nmesh.faces[:]
1499 progress.setActivity("HSR: Newell", len(facelist))
1501 while len(facelist):
1508 for Q in facelist[1:]:
1510 debug("P.smooth: " + str(P.smooth) + "\n")
1511 debug("Q.smooth: " + str(Q.smooth) + "\n")
1518 # We need to test only those Qs whose furthest vertex
1519 # is closer to the observer than the closest vertex of P.
1521 zP = [v.co[2] for v in P.v]
1522 zQ = [v.co[2] for v in Q.v]
1523 ZOverlap = min(zP) < max(zQ)
1527 # We can safely print P
1532 # Test 1: X extent overlapping
1533 xP = [v.co[0] for v in P.v]
1534 xQ = [v.co[0] for v in Q.v]
1535 notXOverlap = (max(xP) < min(xQ)) or (max(xQ) < min(xP))
1540 # Test 2: Y extent Overlapping
1541 yP = [v.co[1] for v in P.v]
1542 yQ = [v.co[1] for v in Q.v]
1543 notYOverlap = (max(yP) < min(yQ)) or (max(yQ) < min(yP))
1549 # Test 3: P vertices are all behind the plane of Q
1552 d = qSign * Distance(Vector(Pi), Q)
1555 pVerticesBehindPlaneQ = (n == len(P))
1557 if pVerticesBehindPlaneQ:
1559 debug("P BEHIND Q!\n")
1563 # Test 4: Q vertices in front of the plane of P
1566 d = pSign * Distance(Vector(Qi), P)
1569 qVerticesInFrontPlaneP = (n == len(Q))
1571 if qVerticesInFrontPlaneP:
1573 debug("Q IN FRONT OF P!\n")
1576 # Test 5: Line Intersections... TODO
1579 # We do not know if P obscures Q.
1581 # Split P or Q, TODO
1582 debug("Split here!!\n")
1586 # The question now is: Does Q obscure P?
1588 # Test 3bis: Q vertices are all behind the plane of P
1591 d = pSign * Distance(Vector(Qi), P)
1594 qVerticesBehindPlaneP = (n == len(Q))
1597 # Test 4bis: P vertices in front of the plane of Q
1600 d = qSign * Distance(Vector(Pi), Q)
1603 pVerticesInFrontPlaneQ = (n == len(P))
1609 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
1610 # Split P or Q, TODO
1611 print "Test 3bis or 4bis failed"
1612 print "Split here!!2\n"
1614 newfaces = intersection.splitOn(nmesh, P, Q, 0)
1626 print "Split here!!\n"
1627 newfaces = intersection.splitOn(nmesh, P, Q, 0)
1639 facelist.insert(0, Q)
1648 nmesh.faces = maplist
1650 for f in nmesh.faces:
1654 def _doHiddenSurfaceRemoval(self, mesh):
1655 """Do HSR for the given mesh.
1657 if len(mesh.faces) == 0:
1660 if config.polygons['HSR'] == 'PAINTER':
1661 print "\n\nUsing the Painter algorithm for HSR.\n"
1662 self.__simpleDepthSort(mesh)
1664 elif config.polygons['HSR'] == 'NEWELL':
1665 print "\n\nUsing the Newell's algorithm for HSR.\n"
1666 self.__newellDepthSort(mesh)
1669 def _doEdgesStyle(self, mesh, edgestyleSelect):
1670 """Process Mesh Edges accroding to a given selection style.
1672 Examples of algorithms:
1675 given an edge if its adjacent faces have the same normal (that is
1676 they are complanar), than deselect it.
1679 given an edge if one its adjacent faces is frontfacing and the
1680 other is backfacing, than select it, else deselect.
1683 Mesh.Mode(Mesh.SelectModes['EDGE'])
1685 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
1687 for i,edge_faces in enumerate(edge_cache):
1688 mesh.edges[i].sel = 0
1689 if edgestyleSelect(edge_faces):
1690 mesh.edges[i].sel = 1
1693 for e in mesh.edges:
1696 if edgestyleSelect(e, mesh):
1702 # ---------------------------------------------------------------------
1704 ## GUI Class and Main Program
1706 # ---------------------------------------------------------------------
1709 from Blender import BGL, Draw
1710 from Blender.BGL import *
1716 # Output Format menu
1717 output_format = config.output['FORMAT']
1718 default_value = outputWriters.keys().index(output_format)+1
1719 GUI.outFormatMenu = Draw.Create(default_value)
1720 GUI.evtOutFormatMenu = 0
1722 # Animation toggle button
1723 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1724 GUI.evtAnimToggle = 1
1726 # Join Objects toggle button
1727 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1728 GUI.evtJoinObjsToggle = 2
1730 # Render filled polygons
1731 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1733 # Shading Style menu
1734 shading_style = config.polygons['SHADING']
1735 default_value = shadingStyles.keys().index(shading_style)+1
1736 GUI.shadingStyleMenu = Draw.Create(default_value)
1737 GUI.evtShadingStyleMenu = 21
1739 GUI.evtPolygonsToggle = 3
1740 # We hide the config.polygons['EXPANSION_TRICK'], for now
1742 # Render polygon edges
1743 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1744 GUI.evtShowEdgesToggle = 4
1746 # Render hidden edges
1747 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1748 GUI.evtShowHiddenEdgesToggle = 5
1751 edge_style = config.edges['STYLE']
1752 default_value = edgeStyles.keys().index(edge_style)+1
1753 GUI.edgeStyleMenu = Draw.Create(default_value)
1754 GUI.evtEdgeStyleMenu = 6
1757 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
1758 GUI.evtEdgeWidthSlider = 7
1761 c = config.edges['COLOR']
1762 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
1763 GUI.evtEdgeColorPicker = 71
1766 GUI.evtRenderButton = 8
1769 GUI.evtExitButton = 9
1773 # initialize static members
1776 glClear(GL_COLOR_BUFFER_BIT)
1777 glColor3f(0.0, 0.0, 0.0)
1778 glRasterPos2i(10, 350)
1779 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
1781 glRasterPos2i(10, 335)
1782 Draw.Text("Press Q or ESC to quit.")
1784 # Build the output format menu
1785 glRasterPos2i(10, 310)
1786 Draw.Text("Select the output Format:")
1787 outMenuStruct = "Output Format %t"
1788 for t in outputWriters.keys():
1789 outMenuStruct = outMenuStruct + "|%s" % t
1790 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1791 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1794 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1795 10, 260, 160, 18, GUI.animToggle.val,
1796 "Toggle rendering of animations")
1798 # Join Objects toggle
1799 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1800 10, 235, 160, 18, GUI.joinObjsToggle.val,
1801 "Join objects in the rendered file")
1804 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1806 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1809 glRasterPos2i(200, 310)
1810 Draw.Text("Rendering Style:")
1813 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1814 200, 285, 160, 18, GUI.polygonsToggle.val,
1815 "Render filled polygons")
1817 if GUI.polygonsToggle.val == 1:
1819 # Polygon Shading Style
1820 shadingStyleMenuStruct = "Shading Style %t"
1821 for t in shadingStyles.keys():
1822 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
1823 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
1824 200, 260, 160, 18, GUI.shadingStyleMenu.val,
1825 "Choose the shading style")
1829 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1830 200, 235, 160, 18, GUI.showEdgesToggle.val,
1831 "Render polygon edges")
1833 if GUI.showEdgesToggle.val == 1:
1836 edgeStyleMenuStruct = "Edge Style %t"
1837 for t in edgeStyles.keys():
1838 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
1839 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1840 200, 210, 160, 18, GUI.edgeStyleMenu.val,
1841 "Choose the edge style")
1844 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1845 200, 185, 140, 18, GUI.edgeWidthSlider.val,
1846 0.0, 10.0, 0, "Change Edge Width")
1849 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
1850 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
1853 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1854 GUI.evtShowHiddenEdgesToggle,
1855 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
1856 "Render hidden edges as dashed lines")
1858 glRasterPos2i(10, 160)
1859 Draw.Text("%s (c) 2006" % __author__)
1861 def event(evt, val):
1863 if evt == Draw.ESCKEY or evt == Draw.QKEY:
1870 def button_event(evt):
1872 if evt == GUI.evtExitButton:
1875 elif evt == GUI.evtOutFormatMenu:
1876 i = GUI.outFormatMenu.val - 1
1877 config.output['FORMAT']= outputWriters.keys()[i]
1879 elif evt == GUI.evtAnimToggle:
1880 config.output['ANIMATION'] = bool(GUI.animToggle.val)
1882 elif evt == GUI.evtJoinObjsToggle:
1883 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
1885 elif evt == GUI.evtPolygonsToggle:
1886 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
1888 elif evt == GUI.evtShadingStyleMenu:
1889 i = GUI.shadingStyleMenu.val - 1
1890 config.polygons['SHADING'] = shadingStyles.keys()[i]
1892 elif evt == GUI.evtShowEdgesToggle:
1893 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
1895 elif evt == GUI.evtShowHiddenEdgesToggle:
1896 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
1898 elif evt == GUI.evtEdgeStyleMenu:
1899 i = GUI.edgeStyleMenu.val - 1
1900 config.edges['STYLE'] = edgeStyles.keys()[i]
1902 elif evt == GUI.evtEdgeWidthSlider:
1903 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
1905 elif evt == GUI.evtEdgeColorPicker:
1906 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
1908 elif evt == GUI.evtRenderButton:
1909 label = "Save %s" % config.output['FORMAT']
1910 # Show the File Selector
1912 Blender.Window.FileSelector(vectorize, label, outputfile)
1915 print "Event: %d not handled!" % evt
1922 from pprint import pprint
1924 pprint(config.output)
1925 pprint(config.polygons)
1926 pprint(config.edges)
1928 _init = staticmethod(_init)
1929 draw = staticmethod(draw)
1930 event = staticmethod(event)
1931 button_event = staticmethod(button_event)
1932 conf_debug = staticmethod(conf_debug)
1934 # A wrapper function for the vectorizing process
1935 def vectorize(filename):
1936 """The vectorizing process is as follows:
1938 - Instanciate the writer and the renderer
1943 print "\nERROR: invalid file name!"
1946 from Blender import Window
1947 editmode = Window.EditMode()
1948 if editmode: Window.EditMode(0)
1950 actualWriter = outputWriters[config.output['FORMAT']]
1951 writer = actualWriter(filename)
1953 renderer = Renderer()
1954 renderer.doRendering(writer, config.output['ANIMATION'])
1956 if editmode: Window.EditMode(1)
1958 # We use a global progress Indicator Object
1962 if __name__ == "__main__":
1967 basename = Blender.sys.basename(Blender.Get('filename'))
1969 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
1971 if Blender.mode == 'background':
1972 progress = ConsoleProgressIndicator()
1973 vectorize(outputfile)
1975 progress = GraphicalProgressIndicator()
1976 Draw.Register(GUI.draw, GUI.event, GUI.button_event)