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
441 self.progressModel = None
443 def setQuiet(self, value):
446 def setActivity(self, name, steps):
447 """Initialize the Model.
449 In a future version (with subactivities-progress support) this method
450 could only set the current activity.
452 self.progressModel = Progress()
453 self.progressModel.setName(name)
454 self.progressModel.setSteps(steps)
456 def getActivity(self):
457 return self.progressModel
460 """Update the model and show the actual progress.
462 assert(self.progressModel)
464 if self.progressModel.update():
468 self.show(self.progressModel.getProgress(),
469 self.progressModel.getName())
471 # We return always True here so we can call the update() method also
472 # from lambda funcs (putting the call in logical AND with other ops)
475 def show(self, progress, name=""):
476 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
477 if self.shows_counter != 0:
481 self.shows_counter = -1
484 class ConsoleProgressIndicator(ProgressIndicator):
485 """Show a progress bar on stderr, a la wget.
488 ProgressIndicator.__init__(self)
490 self.swirl_chars = ["-", "\\", "|", "/"]
491 self.swirl_count = -1
493 def show(self, progress, name):
494 ProgressIndicator.show(self, progress, name)
497 bar_progress = int( (progress/100.0) * bar_length )
498 bar = ("=" * bar_progress).ljust(bar_length)
500 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
501 swirl_char = self.swirl_chars[self.swirl_count]
503 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
505 sys.stderr.write(progress_bar+"\r")
507 sys.stderr.write("\n")
510 class GraphicalProgressIndicator(ProgressIndicator):
511 """Interface to the Blender.Window.DrawProgressBar() method.
514 ProgressIndicator.__init__(self)
516 #self.swirl_chars = ["-", "\\", "|", "/"]
517 # We have to use letters with the same width, for now!
518 # Blender progress bar considers the font widths when
519 # calculating the progress bar width.
520 self.swirl_chars = ["\\", "/"]
521 self.swirl_count = -1
523 def show(self, progress, name):
524 ProgressIndicator.show(self, progress)
526 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
527 swirl_char = self.swirl_chars[self.swirl_count]
529 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
531 # Finally draw the Progress Bar
532 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
533 Window.DrawProgressBar(progress/100.0, progress_text)
536 Window.DrawProgressBar(1, progress_text)
541 # ---------------------------------------------------------------------
543 ## 2D Object representation class
545 # ---------------------------------------------------------------------
547 # TODO: a class to represent the needed properties of a 2D vector image
548 # For now just using a [N]Mesh structure.
551 # ---------------------------------------------------------------------
553 ## Vector Drawing Classes
555 # ---------------------------------------------------------------------
561 A class for printing output in a vectorial format.
563 Given a 2D representation of the 3D scene the class is responsible to
564 write it is a vector format.
566 Every subclasses of VectorWriter must have at last the following public
570 - printCanvas(self, scene,
571 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
574 def __init__(self, fileName):
575 """Set the output file name and other properties"""
577 self.outputFileName = fileName
580 context = Scene.GetCurrent().getRenderingContext()
581 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
585 self.animation = False
592 def open(self, startFrame=1, endFrame=1):
593 if startFrame != endFrame:
594 self.startFrame = startFrame
595 self.endFrame = endFrame
596 self.animation = True
598 self.file = open(self.outputFileName, "w")
599 print "Outputting to: ", self.outputFileName
607 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
608 showHiddenEdges=False):
609 """This is the interface for the needed printing routine.
616 class SVGVectorWriter(VectorWriter):
617 """A concrete class for writing SVG output.
620 def __init__(self, fileName):
621 """Simply call the parent Contructor.
623 VectorWriter.__init__(self, fileName)
630 def open(self, startFrame=1, endFrame=1):
631 """Do some initialization operations.
633 VectorWriter.open(self, startFrame, endFrame)
637 """Do some finalization operation.
641 # remember to call the close method of the parent
642 VectorWriter.close(self)
645 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
646 showHiddenEdges=False):
647 """Convert the scene representation to SVG.
650 Objects = scene.getChildren()
652 context = scene.getRenderingContext()
653 framenumber = context.currentFrame()
656 framestyle = "display:none"
658 framestyle = "display:block"
660 # Assign an id to this group so we can set properties on it using DOM
661 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
662 (framenumber, framestyle) )
667 if(obj.getType() != 'Mesh'):
670 self.file.write("<g id=\"%s\">\n" % obj.getName())
672 mesh = obj.getData(mesh=1)
675 self._printPolygons(mesh)
678 self._printEdges(mesh, showHiddenEdges)
680 self.file.write("</g>\n")
682 self.file.write("</g>\n")
689 def _calcCanvasCoord(self, v):
690 """Convert vertex in scene coordinates to canvas coordinates.
693 pt = Vector([0, 0, 0])
695 mW = float(self.canvasSize[0])/2.0
696 mH = float(self.canvasSize[1])/2.0
698 # rescale to canvas size
699 pt[0] = v.co[0]*mW + mW
700 pt[1] = v.co[1]*mH + mH
703 # For now we want (0,0) in the top-left corner of the canvas.
704 # Mirror and translate along y
706 pt[1] += self.canvasSize[1]
710 def _printHeader(self):
711 """Print SVG header."""
713 self.file.write("<?xml version=\"1.0\"?>\n")
714 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
715 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
716 self.file.write("<svg version=\"1.0\"\n")
717 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
718 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
723 self.file.write("""\n<script type="text/javascript"><![CDATA[
727 /* FIXME: Use 1000 as interval as lower values gives problems */
728 timerID = setInterval("NextFrame()", 1000);
729 globalFrameCounter=%d;
733 currentElement = document.getElementById('frame'+globalFrameCounter)
734 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
741 if (globalFrameCounter > globalEndFrame)
743 clearInterval(timerID)
749 previousElement.style.display="none";
751 currentElement.style.display="block";
752 globalFrameCounter++;
756 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
758 def _printFooter(self):
759 """Print the SVG footer."""
761 self.file.write("\n</svg>\n")
763 def _printPolygons(self, mesh):
764 """Print the selected (visible) polygons.
767 if len(mesh.faces) == 0:
770 self.file.write("<g>\n")
772 for face in mesh.faces:
776 self.file.write("<path d=\"")
778 p = self._calcCanvasCoord(face.verts[0])
779 self.file.write("M %g,%g L " % (p[0], p[1]))
781 for v in face.verts[1:]:
782 p = self._calcCanvasCoord(v)
783 self.file.write("%g,%g " % (p[0], p[1]))
785 # get rid of the last blank space, just cosmetics here.
786 self.file.seek(-1, 1)
787 self.file.write(" z\"\n")
789 # take as face color the first vertex color
792 color = [fcol.r, fcol.g, fcol.b, fcol.a]
794 color = [255, 255, 255, 255]
796 # Convert the color to the #RRGGBB form
797 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
799 # Handle transparent polygons
802 opacity = float(color[3])/255.0
803 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
805 self.file.write("\tstyle=\"fill:" + str_col + ";")
806 self.file.write(opacity_string)
808 # use the stroke property to alleviate the "adjacent edges" problem,
809 # we simulate polygon expansion using borders,
810 # see http://www.antigrain.com/svg/index.html for more info
813 if config.polygons['EXPANSION_TRICK']:
814 str_col = "#000000" # For debug
815 self.file.write(" stroke:%s;\n" % str_col)
816 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
817 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
819 self.file.write("\"/>\n")
821 self.file.write("</g>\n")
823 def _printEdges(self, mesh, showHiddenEdges=False):
824 """Print the wireframe using mesh edges.
827 stroke_width = config.edges['WIDTH']
828 stroke_col = config.edges['COLOR']
830 self.file.write("<g>\n")
834 hidden_stroke_style = ""
837 if showHiddenEdges == False:
840 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
842 p1 = self._calcCanvasCoord(e.v1)
843 p2 = self._calcCanvasCoord(e.v2)
845 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
846 % ( p1[0], p1[1], p2[0], p2[1] ) )
847 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
848 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
849 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
850 self.file.write(hidden_stroke_style)
851 self.file.write("\"/>\n")
853 self.file.write("</g>\n")
856 # ---------------------------------------------------------------------
860 # ---------------------------------------------------------------------
862 # A dictionary to collect different shading style methods
863 shadingStyles = dict()
864 shadingStyles['FLAT'] = None
865 shadingStyles['TOON'] = None
867 # A dictionary to collect different edge style methods
869 edgeStyles['MESH'] = MeshUtils.isMeshEdge
870 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
872 # A dictionary to collect the supported output formats
873 outputWriters = dict()
874 outputWriters['SVG'] = SVGVectorWriter
878 """Render a scene viewed from the active camera.
880 This class is responsible of the rendering process, transformation and
881 projection of the objects in the scene are invoked by the renderer.
883 The rendering is done using the active camera for the current scene.
887 """Make the rendering process only for the current scene by default.
889 We will work on a copy of the scene, to be sure that the current scene do
890 not get modified in any way.
893 # Render the current Scene, this should be a READ-ONLY property
894 self._SCENE = Scene.GetCurrent()
896 # Use the aspect ratio of the scene rendering context
897 context = self._SCENE.getRenderingContext()
899 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
900 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
901 float(context.aspectRatioY())
904 # Render from the currently active camera
905 self.cameraObj = self._SCENE.getCurrentCamera()
907 # Get a projector for this camera.
908 # NOTE: the projector wants object in world coordinates,
909 # so we should remember to apply modelview transformations
910 # _before_ we do projection transformations.
911 self.proj = Projector(self.cameraObj, self.canvasRatio)
913 # Get the list of lighting sources
914 obj_lst = self._SCENE.getChildren()
915 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
917 # When there are no lights we use a default lighting source
918 # that have the same position of the camera
919 if len(self.lights) == 0:
921 lobj = Object.New('Lamp')
922 lobj.loc = self.cameraObj.loc
924 self.lights.append(lobj)
931 def doRendering(self, outputWriter, animation=False):
932 """Render picture or animation and write it out.
935 - a Vector writer object that will be used to output the result.
936 - a flag to tell if we want to render an animation or only the
940 context = self._SCENE.getRenderingContext()
941 origCurrentFrame = context.currentFrame()
943 # Handle the animation case
945 startFrame = origCurrentFrame
946 endFrame = startFrame
949 startFrame = context.startFrame()
950 endFrame = context.endFrame()
951 outputWriter.open(startFrame, endFrame)
953 # Do the rendering process frame by frame
954 print "Start Rendering of %d frames" % (endFrame-startFrame)
955 for f in xrange(startFrame, endFrame+1):
956 print "\n\nFrame: %d" % f
957 context.currentFrame(f)
959 # Use some temporary workspace, a full copy of the scene
960 inputScene = self._SCENE.copy(2)
961 # And Set our camera accordingly
962 self.cameraObj = inputScene.getCurrentCamera()
965 renderedScene = self.doRenderScene(inputScene)
967 print "There was an error! Aborting."
969 print traceback.print_exc()
971 self._SCENE.makeCurrent()
972 Scene.unlink(inputScene)
976 outputWriter.printCanvas(renderedScene,
977 doPrintPolygons = config.polygons['SHOW'],
978 doPrintEdges = config.edges['SHOW'],
979 showHiddenEdges = config.edges['SHOW_HIDDEN'])
981 # delete the rendered scene
982 self._SCENE.makeCurrent()
983 Scene.unlink(renderedScene)
988 context.currentFrame(origCurrentFrame)
991 def doRenderScene(self, workScene):
992 """Control the rendering process.
994 Here we control the entire rendering process invoking the operation
995 needed to transform and project the 3D scene in two dimensions.
998 # global processing of the scene
1000 self._doSceneClipping(workScene)
1002 self._doConvertGeometricObjsToMesh(workScene)
1004 if config.output['JOIN_OBJECTS']:
1005 self._joinMeshObjectsInScene(workScene)
1007 self._doSceneDepthSorting(workScene)
1009 # Per object activities
1011 Objects = workScene.getChildren()
1012 print "Total Objects: %d" % len(Objects)
1013 for i,obj in enumerate(Objects):
1015 print "Rendering Object: %d" % i
1017 if obj.getType() != 'Mesh':
1018 print "Only Mesh supported! - Skipping type:", obj.getType()
1021 print "Rendering: ", obj.getName()
1023 mesh = obj.getData(mesh=1)
1025 self._doModelingTransformation(mesh, obj.matrix)
1027 self._doBackFaceCulling(mesh)
1029 self._doLighting(mesh)
1031 # Do "projection" now so we perform further processing
1032 # in Normalized View Coordinates
1033 self._doProjection(mesh, self.proj)
1035 self._doViewFrustumClipping(mesh)
1037 self._doHiddenSurfaceRemoval(mesh)
1039 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
1042 # Update the object data, important! :)
1054 def _getObjPosition(self, obj):
1055 """Return the obj position in World coordinates.
1057 return obj.matrix.translationPart()
1059 def _cameraViewVector(self):
1060 """Get the View Direction form the camera matrix.
1062 return Vector(self.cameraObj.matrix[2]).resize3D()
1067 def _isFaceVisible(self, face):
1068 """Determine if a face of an object is visible from the current camera.
1070 The view vector is calculated from the camera location and one of the
1071 vertices of the face (expressed in World coordinates, after applying
1072 modelview transformations).
1074 After those transformations we determine if a face is visible by
1075 computing the angle between the face normal and the view vector, this
1076 angle has to be between -90 and 90 degrees for the face to be visible.
1077 This corresponds somehow to the dot product between the two, if it
1078 results > 0 then the face is visible.
1080 There is no need to normalize those vectors since we are only interested in
1081 the sign of the cross product and not in the product value.
1083 NOTE: here we assume the face vertices are in WorldCoordinates, so
1084 please transform the object _before_ doing the test.
1087 normal = Vector(face.no)
1088 camPos = self._getObjPosition(self.cameraObj)
1091 # View Vector in orthographics projections is the view Direction of
1093 if self.cameraObj.data.getType() == 1:
1094 view_vect = self._cameraViewVector()
1096 # View vector in perspective projections can be considered as
1097 # the difference between the camera position and one point of
1098 # the face, we choose the farthest point from the camera.
1099 if self.cameraObj.data.getType() == 0:
1100 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
1104 # if d > 0 the face is visible from the camera
1105 d = view_vect * normal
1115 def _doSceneClipping(self, scene):
1116 """Clip whole objects against the View Frustum.
1118 For now clip away only objects according to their center position.
1121 cpos = self._getObjPosition(self.cameraObj)
1122 view_vect = self._cameraViewVector()
1124 near = self.cameraObj.data.clipStart
1125 far = self.cameraObj.data.clipEnd
1127 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
1128 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
1129 fovy = fovy * 360.0/pi
1131 Objects = scene.getChildren()
1133 if o.getType() != 'Mesh': continue;
1135 obj_vect = Vector(cpos) - self._getObjPosition(o)
1137 d = obj_vect*view_vect
1138 theta = AngleBetweenVecs(obj_vect, view_vect)
1140 # if the object is outside the view frustum, clip it away
1141 if (d < near) or (d > far) or (theta > fovy):
1144 def _doConvertGeometricObjsToMesh(self, scene):
1145 """Convert all "geometric" objects to mesh ones.
1147 #geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
1148 geometricObjTypes = ['Mesh', 'Surf', 'Curve']
1150 Objects = scene.getChildren()
1151 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
1154 obj = self._convertToRawMeshObj(obj)
1156 scene.unlink(old_obj)
1159 # XXX Workaround for Text and Curve which have some normals
1160 # inverted when they are converted to Mesh, REMOVE that when
1161 # blender will fix that!!
1162 if old_obj.getType() in ['Curve', 'Text']:
1163 me = obj.getData(mesh=1)
1164 for f in me.faces: f.sel = 1;
1165 for v in me.verts: v.sel = 1;
1172 def _doSceneDepthSorting(self, scene):
1173 """Sort objects in the scene.
1175 The object sorting is done accordingly to the object centers.
1178 c = self._getObjPosition(self.cameraObj)
1180 by_center_pos = (lambda o1, o2:
1181 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
1182 cmp((self._getObjPosition(o1) - Vector(c)).length,
1183 (self._getObjPosition(o2) - Vector(c)).length)
1186 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
1187 # then ob1 goes farther than obj2, useful when obj2 has holes
1190 Objects = scene.getChildren()
1191 Objects.sort(by_center_pos)
1198 def _joinMeshObjectsInScene(self, scene):
1199 """Merge all the Mesh Objects in a scene into a single Mesh Object.
1202 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
1204 # FIXME: Object.join() do not work if the list contains 1 object
1208 mesh = Mesh.New('BigOne')
1209 bigObj = Object.New('Mesh', 'BigOne')
1216 except RuntimeError:
1217 print "\nWarning! - Can't Join Objects\n"
1218 scene.unlink(bigObj)
1221 print "Objects Type error?"
1229 # Per object/mesh methods
1231 def _convertToRawMeshObj(self, object):
1232 """Convert geometry based object to a mesh object.
1234 me = Mesh.New('RawMesh_'+object.name)
1235 me.getFromObject(object.name)
1237 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1240 # If the object has no materials set a default material
1241 if not me.materials:
1242 me.materials = [Material.New()]
1243 #for f in me.faces: f.mat = 0
1245 newObject.setMatrix(object.getMatrix())
1249 def _doModelingTransformation(self, mesh, matrix):
1250 """Transform object coordinates to world coordinates.
1252 This step is done simply applying to the object its tranformation
1253 matrix and recalculating its normals.
1255 # XXX FIXME: blender do not transform normals in the right way when
1256 # there are negative scale values
1257 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1258 print "WARNING: Negative scales, expect incorrect results!"
1260 mesh.transform(matrix, True)
1262 def _doBackFaceCulling(self, mesh):
1263 """Simple Backface Culling routine.
1265 At this level we simply do a visibility test face by face and then
1266 select the vertices belonging to visible faces.
1269 # Select all vertices, so edges can be displayed even if there are no
1271 for v in mesh.verts:
1274 Mesh.Mode(Mesh.SelectModes['FACE'])
1276 for f in mesh.faces:
1278 if self._isFaceVisible(f):
1281 def _doLighting(self, mesh):
1282 """Apply an Illumination and shading model to the object.
1284 The model used is the Phong one, it may be inefficient,
1285 but I'm just learning about rendering and starting from Phong seemed
1286 the most natural way.
1289 # If the mesh has vertex colors already, use them,
1290 # otherwise turn them on and do some calculations
1291 if mesh.vertexColors:
1293 mesh.vertexColors = 1
1295 materials = mesh.materials
1297 camPos = self._getObjPosition(self.cameraObj)
1299 # We do per-face color calculation (FLAT Shading), we can easily turn
1300 # to a per-vertex calculation if we want to implement some shading
1301 # technique. For an example see:
1302 # http://www.miralab.unige.ch/papers/368.pdf
1303 for f in mesh.faces:
1309 mat = materials[f.mat]
1311 # A new default material
1313 mat = Material.New('defMat')
1315 # Check if it is a shadeless material
1316 elif mat.getMode() & Material.Modes['SHADELESS']:
1318 # Convert to a value between 0 and 255
1319 tmp_col = [ int(c * 255.0) for c in I]
1330 # do vertex color calculation
1332 TotDiffSpec = Vector([0.0, 0.0, 0.0])
1334 for l in self.lights:
1336 light_pos = self._getObjPosition(l)
1337 light = light_obj.data
1339 L = Vector(light_pos).normalize()
1341 V = (Vector(camPos) - Vector(f.cent)).normalize()
1343 N = Vector(f.no).normalize()
1345 if config.polygons['SHADING'] == 'TOON':
1346 NL = ShadingUtils.toonShading(N*L)
1350 # Should we use NL instead of (N*L) here?
1351 R = 2 * (N*L) * N - L
1353 Ip = light.getEnergy()
1355 # Diffuse co-efficient
1356 kd = mat.getRef() * Vector(mat.getRGBCol())
1358 kd[i] *= light.col[i]
1360 Idiff = Ip * kd * max(0, NL)
1363 # Specular component
1364 ks = mat.getSpec() * Vector(mat.getSpecCol())
1365 ns = mat.getHardness()
1366 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1368 TotDiffSpec += (Idiff+Ispec)
1372 Iamb = Vector(Blender.World.Get()[0].getAmb())
1375 # Emissive component (convert to a triplet)
1376 ki = Vector([mat.getEmit()]*3)
1378 #I = ki + Iamb + (Idiff + Ispec)
1379 I = ki + (ka * Iamb) + TotDiffSpec
1382 # Set Alpha component
1384 I.append(mat.getAlpha())
1386 # Clamp I values between 0 and 1
1387 I = [ min(c, 1) for c in I]
1388 I = [ max(0, c) for c in I]
1390 # Convert to a value between 0 and 255
1391 tmp_col = [ int(c * 255.0) for c in I]
1399 def _doProjection(self, mesh, projector):
1400 """Apply Viewing and Projection tranformations.
1403 for v in mesh.verts:
1404 p = projector.doProjection(v.co[:])
1409 #mesh.recalcNormals()
1412 # We could reeset Camera matrix, since now
1413 # we are in Normalized Viewing Coordinates,
1414 # but doung that would affect World Coordinate
1415 # processing for other objects
1417 #self.cameraObj.data.type = 1
1418 #self.cameraObj.data.scale = 2.0
1419 #m = Matrix().identity()
1420 #self.cameraObj.setMatrix(m)
1422 def _doViewFrustumClipping(self, mesh):
1423 """Clip faces against the View Frustum.
1427 def __simpleDepthSort(self, mesh):
1428 """Sort faces by the furthest vertex.
1430 This simple mesthod is known also as the painter algorithm, and it
1431 solves HSR correctly only for convex meshes.
1435 # The sorting requires circa n*log(n) steps
1437 progress.setActivity("HSR: Painter", n*log(n))
1440 by_furthest_z = (lambda f1, f2: progress.update() and
1441 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1444 # FIXME: using NMesh to sort faces. We should avoid that!
1445 nmesh = NMesh.GetRaw(mesh.name)
1447 # remember that _higher_ z values mean further points
1448 nmesh.faces.sort(by_furthest_z)
1449 nmesh.faces.reverse()
1453 def __topologicalDepthSort(self, mesh):
1454 """Occlusion based on topological occlusion.
1456 Build the occlusion graph of the mesh,
1457 and then do topological sort on that graph
1461 def __newellDepthSort(self, mesh):
1462 """Newell's depth sorting.
1465 by_furthest_z = (lambda f1, f2:
1466 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1470 def isOnSegment(v1, v2, p):
1472 # when p is at extreme points
1473 if p == v1 or p == v2:
1483 print "l: ", l, " l1: ", l1, " l2: ", l2, "diff: %.9f" % (l - (l1+l2) )
1485 if abs(l - (l1+l2)) < EPS:
1492 def Distance(point, face):
1493 """ Calculate the distance between a point and a face.
1495 An alternative but more expensive method can be:
1497 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
1498 Vector(face.no), Vector(point), 0)
1500 d = Vector(ip - point).length
1503 plNormal = Vector(face.no)
1504 plVert0 = Vector(face[0])
1506 #d = abs( (point * plNormal ) - (plVert0 * plNormal) )
1507 d = (point * plNormal ) - (plVert0 * plNormal)
1508 debug("d: %.10f - sel: %d, %s\n" % (d, face.sel, str(point)) )
1513 # FIXME: using NMesh to sort faces. We should avoid that!
1514 nmesh = NMesh.GetRaw(mesh.name)
1516 # remember that _higher_ z values mean further points
1517 nmesh.faces.sort(by_furthest_z)
1518 nmesh.faces.reverse()
1521 # Begin depth sort tests
1523 # use the smooth flag to set marked faces
1524 for f in nmesh.faces:
1527 facelist = nmesh.faces[:]
1534 progress.setActivity("HSR: Newell", len(facelist))
1535 progress.setQuiet(True)
1537 #while len(facelist)-1:
1538 while len(facelist):
1546 for Q in facelist[1:]:
1548 debug("P.smooth: " + str(P.smooth) + "\n")
1549 debug("Q.smooth: " + str(Q.smooth) + "\n")
1556 # We need to test only those Qs whose furthest vertex
1557 # is closer to the observer than the closest vertex of P.
1559 zP = [v.co[2] for v in P.v]
1560 zQ = [v.co[2] for v in Q.v]
1561 ZOverlap = min(zP) < max(zQ)
1565 debug("NOT Z OVERLAP!\n")
1567 # We can safely print P
1572 # Test 1: X extent overlapping
1573 xP = [v.co[0] for v in P.v]
1574 xQ = [v.co[0] for v in Q.v]
1575 notXOverlap = (max(xP) < min(xQ)) or (max(xQ) < min(xP))
1579 debug("NOT X OVERLAP!\n")
1582 # Test 2: Y extent Overlapping
1583 yP = [v.co[1] for v in P.v]
1584 yQ = [v.co[1] for v in Q.v]
1585 notYOverlap = (max(yP) < min(yQ)) or (max(yQ) < min(yP))
1589 debug("NOT Y OVERLAP!\n")
1593 # Test 3: P vertices are all behind the plane of Q
1597 d = qSign * Distance(Vector(Pi), Q)
1600 pVerticesBehindPlaneQ = (n == len(P))
1602 if pVerticesBehindPlaneQ:
1604 debug("P BEHIND Q!\n")
1608 # Test 4: Q vertices in front of the plane of P
1612 d = pSign * Distance(Vector(Qi), P)
1615 qVerticesInFrontPlaneP = (n == len(Q))
1617 if qVerticesInFrontPlaneP:
1619 debug("Q IN FRONT OF P!\n")
1622 # Test 5: Line Intersections... TODO
1623 # Check if polygons effectively overlap each other, not only
1624 # boundig boxes as dome before.
1625 # Since we We are working in normalized projection coordinates
1626 # we kust check if polygons intersect.
1628 def projectionsOverlap(P, Q):
1630 for i in range(0, len(P.v)):
1632 v1 = Vector(P.v[i-1])
1637 for j in range(0, len(Q.v)):
1638 v3 = Vector(Q.v[j-1])
1643 ret = LineIntersect(v1, v2, v3, v4)
1644 # if line v1-v2 and v3-v4 intersect both return
1645 # values are the same.
1646 if ret and ret[0] == ret[1] and isOnSegment(v1, v2,
1647 ret[0]) and isOnSegment(v3, v4, ret[1]):
1648 debug("Projections OVERLAP!!\n")
1650 " M "+ str(v1[0])+','+str(v1[1]) + ' L ' + str(v2[0])+','+str(v2[1]) + '\n' +
1651 " M "+ str(v3[0])+','+str(v3[1]) + ' L ' + str(v4[0])+','+str(v4[1]) + '\n' +
1653 debug("return: "+ str(ret)+"\n")
1658 if not projectionsOverlap(P, Q):
1660 debug("Projections do not overlap!\n")
1664 # We do not know if P obscures Q.
1666 # Split P or Q, TODO
1667 debug("Cycle detected!\n")
1668 debug("Split here!!\n")
1672 # The question now is: Does Q obscure P?
1674 # Test 3bis: Q vertices are all behind the plane of P
1678 d = pSign * Distance(Vector(Qi), P)
1681 qVerticesBehindPlaneP = (n == len(Q))
1683 if qVerticesBehindPlaneP:
1684 debug("\nTest 3bis\n")
1685 debug("Q BEHIND P!\n")
1688 # Test 4bis: P vertices in front of the plane of Q
1692 d = qSign * Distance(Vector(Pi), Q)
1695 pVerticesInFrontPlaneQ = (n == len(P))
1697 if pVerticesInFrontPlaneQ:
1698 debug("\nTest 4bis\n")
1699 debug("P IN FRONT OF Q!\n")
1704 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
1705 debug("\nSimple Intersection?\n")
1706 # Split P or Q, TODO
1707 print "Test 3bis or 4bis failed"
1708 print "Split here!!2\n"
1710 """newfaces = intersection.splitOn(P, Q, 0)
1724 print "Split here!!\n"
1726 newfaces = intersection.splitOn(P, Q, 0)
1738 facelist.insert(0, Q)
1748 nmesh.faces = maplist
1750 for f in nmesh.faces:
1754 def _doHiddenSurfaceRemoval(self, mesh):
1755 """Do HSR for the given mesh.
1757 if len(mesh.faces) == 0:
1760 if config.polygons['HSR'] == 'PAINTER':
1761 print "\nUsing the Painter algorithm for HSR."
1762 self.__simpleDepthSort(mesh)
1764 elif config.polygons['HSR'] == 'NEWELL':
1765 print "\nUsing the Newell's algorithm for HSR."
1766 self.__newellDepthSort(mesh)
1769 def _doEdgesStyle(self, mesh, edgestyleSelect):
1770 """Process Mesh Edges accroding to a given selection style.
1772 Examples of algorithms:
1775 given an edge if its adjacent faces have the same normal (that is
1776 they are complanar), than deselect it.
1779 given an edge if one its adjacent faces is frontfacing and the
1780 other is backfacing, than select it, else deselect.
1783 Mesh.Mode(Mesh.SelectModes['EDGE'])
1785 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
1787 for i,edge_faces in enumerate(edge_cache):
1788 mesh.edges[i].sel = 0
1789 if edgestyleSelect(edge_faces):
1790 mesh.edges[i].sel = 1
1793 for e in mesh.edges:
1796 if edgestyleSelect(e, mesh):
1802 # ---------------------------------------------------------------------
1804 ## GUI Class and Main Program
1806 # ---------------------------------------------------------------------
1809 from Blender import BGL, Draw
1810 from Blender.BGL import *
1816 # Output Format menu
1817 output_format = config.output['FORMAT']
1818 default_value = outputWriters.keys().index(output_format)+1
1819 GUI.outFormatMenu = Draw.Create(default_value)
1820 GUI.evtOutFormatMenu = 0
1822 # Animation toggle button
1823 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1824 GUI.evtAnimToggle = 1
1826 # Join Objects toggle button
1827 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1828 GUI.evtJoinObjsToggle = 2
1830 # Render filled polygons
1831 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1833 # Shading Style menu
1834 shading_style = config.polygons['SHADING']
1835 default_value = shadingStyles.keys().index(shading_style)+1
1836 GUI.shadingStyleMenu = Draw.Create(default_value)
1837 GUI.evtShadingStyleMenu = 21
1839 GUI.evtPolygonsToggle = 3
1840 # We hide the config.polygons['EXPANSION_TRICK'], for now
1842 # Render polygon edges
1843 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1844 GUI.evtShowEdgesToggle = 4
1846 # Render hidden edges
1847 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1848 GUI.evtShowHiddenEdgesToggle = 5
1851 edge_style = config.edges['STYLE']
1852 default_value = edgeStyles.keys().index(edge_style)+1
1853 GUI.edgeStyleMenu = Draw.Create(default_value)
1854 GUI.evtEdgeStyleMenu = 6
1857 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
1858 GUI.evtEdgeWidthSlider = 7
1861 c = config.edges['COLOR']
1862 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
1863 GUI.evtEdgeColorPicker = 71
1866 GUI.evtRenderButton = 8
1869 GUI.evtExitButton = 9
1873 # initialize static members
1876 glClear(GL_COLOR_BUFFER_BIT)
1877 glColor3f(0.0, 0.0, 0.0)
1878 glRasterPos2i(10, 350)
1879 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
1881 glRasterPos2i(10, 335)
1882 Draw.Text("Press Q or ESC to quit.")
1884 # Build the output format menu
1885 glRasterPos2i(10, 310)
1886 Draw.Text("Select the output Format:")
1887 outMenuStruct = "Output Format %t"
1888 for t in outputWriters.keys():
1889 outMenuStruct = outMenuStruct + "|%s" % t
1890 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1891 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1894 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1895 10, 260, 160, 18, GUI.animToggle.val,
1896 "Toggle rendering of animations")
1898 # Join Objects toggle
1899 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1900 10, 235, 160, 18, GUI.joinObjsToggle.val,
1901 "Join objects in the rendered file")
1904 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1906 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1909 glRasterPos2i(200, 310)
1910 Draw.Text("Rendering Style:")
1913 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1914 200, 285, 160, 18, GUI.polygonsToggle.val,
1915 "Render filled polygons")
1917 if GUI.polygonsToggle.val == 1:
1919 # Polygon Shading Style
1920 shadingStyleMenuStruct = "Shading Style %t"
1921 for t in shadingStyles.keys():
1922 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
1923 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
1924 200, 260, 160, 18, GUI.shadingStyleMenu.val,
1925 "Choose the shading style")
1929 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1930 200, 235, 160, 18, GUI.showEdgesToggle.val,
1931 "Render polygon edges")
1933 if GUI.showEdgesToggle.val == 1:
1936 edgeStyleMenuStruct = "Edge Style %t"
1937 for t in edgeStyles.keys():
1938 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
1939 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1940 200, 210, 160, 18, GUI.edgeStyleMenu.val,
1941 "Choose the edge style")
1944 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1945 200, 185, 140, 18, GUI.edgeWidthSlider.val,
1946 0.0, 10.0, 0, "Change Edge Width")
1949 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
1950 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
1953 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1954 GUI.evtShowHiddenEdgesToggle,
1955 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
1956 "Render hidden edges as dashed lines")
1958 glRasterPos2i(10, 160)
1959 Draw.Text("%s (c) 2006" % __author__)
1961 def event(evt, val):
1963 if evt == Draw.ESCKEY or evt == Draw.QKEY:
1970 def button_event(evt):
1972 if evt == GUI.evtExitButton:
1975 elif evt == GUI.evtOutFormatMenu:
1976 i = GUI.outFormatMenu.val - 1
1977 config.output['FORMAT']= outputWriters.keys()[i]
1979 elif evt == GUI.evtAnimToggle:
1980 config.output['ANIMATION'] = bool(GUI.animToggle.val)
1982 elif evt == GUI.evtJoinObjsToggle:
1983 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
1985 elif evt == GUI.evtPolygonsToggle:
1986 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
1988 elif evt == GUI.evtShadingStyleMenu:
1989 i = GUI.shadingStyleMenu.val - 1
1990 config.polygons['SHADING'] = shadingStyles.keys()[i]
1992 elif evt == GUI.evtShowEdgesToggle:
1993 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
1995 elif evt == GUI.evtShowHiddenEdgesToggle:
1996 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
1998 elif evt == GUI.evtEdgeStyleMenu:
1999 i = GUI.edgeStyleMenu.val - 1
2000 config.edges['STYLE'] = edgeStyles.keys()[i]
2002 elif evt == GUI.evtEdgeWidthSlider:
2003 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
2005 elif evt == GUI.evtEdgeColorPicker:
2006 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
2008 elif evt == GUI.evtRenderButton:
2009 label = "Save %s" % config.output['FORMAT']
2010 # Show the File Selector
2012 Blender.Window.FileSelector(vectorize, label, outputfile)
2015 print "Event: %d not handled!" % evt
2022 from pprint import pprint
2024 pprint(config.output)
2025 pprint(config.polygons)
2026 pprint(config.edges)
2028 _init = staticmethod(_init)
2029 draw = staticmethod(draw)
2030 event = staticmethod(event)
2031 button_event = staticmethod(button_event)
2032 conf_debug = staticmethod(conf_debug)
2034 # A wrapper function for the vectorizing process
2035 def vectorize(filename):
2036 """The vectorizing process is as follows:
2038 - Instanciate the writer and the renderer
2043 print "\nERROR: invalid file name!"
2046 from Blender import Window
2047 editmode = Window.EditMode()
2048 if editmode: Window.EditMode(0)
2050 actualWriter = outputWriters[config.output['FORMAT']]
2051 writer = actualWriter(filename)
2053 renderer = Renderer()
2054 renderer.doRendering(writer, config.output['ANIMATION'])
2056 if editmode: Window.EditMode(1)
2058 # We use a global progress Indicator Object
2062 if __name__ == "__main__":
2067 basename = Blender.sys.basename(Blender.Get('filename'))
2069 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
2071 if Blender.mode == 'background':
2072 progress = ConsoleProgressIndicator()
2073 vectorize(outputfile)
2075 progress = GraphicalProgressIndicator()
2076 Draw.Register(GUI.draw, GUI.event, GUI.button_event)