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
120 sys.stderr.write(msg)
131 # ---------------------------------------------------------------------
133 ## Mesh Utility class
135 # ---------------------------------------------------------------------
138 def buildEdgeFaceUsersCache(me):
140 Takes a mesh and returns a list aligned with the meshes edges.
141 Each item is a list of the faces that use the edge
142 would be the equiv for having ed.face_users as a property
144 Taken from .blender/scripts/bpymodules/BPyMesh.py,
145 thanks to ideasman_42.
148 def sorted_edge_indicies(ed):
156 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
158 fvi= [v.index for v in f.v]# face vert idx's
159 for i in xrange(len(f)):
166 face_edges_dict[i1,i2][1].append(f)
168 face_edges= [None] * len(me.edges)
169 for ed_index, ed_faces in face_edges_dict.itervalues():
170 face_edges[ed_index]= ed_faces
174 def isMeshEdge(adjacent_faces):
177 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
178 Note: if the edge has no adjacent faces we want to show it as well,
179 useful for "edge only" portion of objects.
182 if len(adjacent_faces) == 0:
185 selected_faces = [f for f in adjacent_faces if f.sel]
187 if len(selected_faces) != 0:
192 def isSilhouetteEdge(adjacent_faces):
193 """Silhuette selection rule.
195 An edge is a silhuette edge if it is shared by two faces with
196 different selection status or if it is a boundary edge of a selected
200 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
201 (len(adjacent_faces) == 2 and
202 adjacent_faces[0].sel != adjacent_faces[1].sel)
208 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
209 isMeshEdge = staticmethod(isMeshEdge)
210 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
213 # ---------------------------------------------------------------------
215 ## Shading Utility class
217 # ---------------------------------------------------------------------
222 def toonShadingMapSetup():
223 levels = config.polygons['TOON_LEVELS']
225 texels = 2*levels - 1
226 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
232 shademap = ShadingUtils.shademap
235 shademap = ShadingUtils.toonShadingMapSetup()
238 for i in xrange(0, len(shademap)-1):
239 pivot = (shademap[i]+shademap[i+1])/2.0
244 if v < shademap[i+1]:
249 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
250 toonShading = staticmethod(toonShading)
253 # ---------------------------------------------------------------------
255 ## Projections classes
257 # ---------------------------------------------------------------------
260 """Calculate the projection of an object given the camera.
262 A projector is useful to so some per-object transformation to obtain the
263 projection of an object given the camera.
265 The main method is #doProjection# see the method description for the
269 def __init__(self, cameraObj, canvasRatio):
270 """Calculate the projection matrix.
272 The projection matrix depends, in this case, on the camera settings.
273 TAKE CARE: This projector expects vertices in World Coordinates!
276 camera = cameraObj.getData()
278 aspect = float(canvasRatio[0])/float(canvasRatio[1])
279 near = camera.clipStart
282 scale = float(camera.scale)
284 fovy = atan(0.5/aspect/(camera.lens/32))
285 fovy = fovy * 360.0/pi
287 # What projection do we want?
289 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
290 elif camera.type == 1:
291 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
293 # View transformation
294 cam = Matrix(cameraObj.getInverseMatrix())
299 self.projectionMatrix = mP
305 def doProjection(self, v):
306 """Project the point on the view plane.
308 Given a vertex calculate the projection using the current projection
312 # Note that we have to work on the vertex using homogeneous coordinates
313 # From blender 2.42+ we don't need to resize the vector to be 4d
314 # when applying a 4x4 matrix, but we do that anyway since we need the
315 # 4th coordinate later
316 p = self.projectionMatrix * Vector(v).resize4D()
318 # Perspective division
335 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
336 """Return a perspective projection matrix.
339 top = near * tan(fovy * pi / 360.0)
343 x = (2.0 * near) / (right-left)
344 y = (2.0 * near) / (top-bottom)
345 a = (right+left) / (right-left)
346 b = (top+bottom) / (top - bottom)
347 c = - ((far+near) / (far-near))
348 d = - ((2*far*near)/(far-near))
354 [0.0, 0.0, -1.0, 0.0])
358 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
359 """Return an orthogonal projection matrix.
362 # The 11 in the formula was found emiprically
363 top = near * tan(fovy * pi / 360.0) * (scale * 11)
365 left = bottom * aspect
370 tx = -((right+left)/rl)
371 ty = -((top+bottom)/tb)
375 [2.0/rl, 0.0, 0.0, tx],
376 [0.0, 2.0/tb, 0.0, ty],
377 [0.0, 0.0, 2.0/fn, tz],
378 [0.0, 0.0, 0.0, 1.0])
383 # ---------------------------------------------------------------------
385 ## Progress Indicator
387 # ---------------------------------------------------------------------
390 """A model for a progress indicator.
392 Do the progress calculation calculation and
393 the view independent stuff of a progress indicator.
395 def __init__(self, steps=0):
401 def setSteps(self, steps):
402 """Set the number of steps of the activity wich we want to track.
409 def setName(self, name):
410 """Set the name of the activity wich we want to track.
417 def getProgress(self):
425 """Update the model, call this method when one step is completed.
427 if self.progress == 100:
431 self.progress = ( float(self.completed) / float(self.steps) ) * 100
432 self.progress = int(self.progress)
437 class ProgressIndicator:
438 """An abstraction of a View for the Progress Model
442 # Use a refresh rate so we do not show the progress at
443 # every update, but every 'self.refresh_rate' times.
444 self.refresh_rate = 10
445 self.shows_counter = 0
449 self.progressModel = None
451 def setQuiet(self, value):
454 def setActivity(self, name, steps):
455 """Initialize the Model.
457 In a future version (with subactivities-progress support) this method
458 could only set the current activity.
460 self.progressModel = Progress()
461 self.progressModel.setName(name)
462 self.progressModel.setSteps(steps)
464 def getActivity(self):
465 return self.progressModel
468 """Update the model and show the actual progress.
470 assert(self.progressModel)
472 if self.progressModel.update():
476 self.show(self.progressModel.getProgress(),
477 self.progressModel.getName())
479 # We return always True here so we can call the update() method also
480 # from lambda funcs (putting the call in logical AND with other ops)
483 def show(self, progress, name=""):
484 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
485 if self.shows_counter != 0:
489 self.shows_counter = -1
492 class ConsoleProgressIndicator(ProgressIndicator):
493 """Show a progress bar on stderr, a la wget.
496 ProgressIndicator.__init__(self)
498 self.swirl_chars = ["-", "\\", "|", "/"]
499 self.swirl_count = -1
501 def show(self, progress, name):
502 ProgressIndicator.show(self, progress, name)
505 bar_progress = int( (progress/100.0) * bar_length )
506 bar = ("=" * bar_progress).ljust(bar_length)
508 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
509 swirl_char = self.swirl_chars[self.swirl_count]
511 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
513 sys.stderr.write(progress_bar+"\r")
515 sys.stderr.write("\n")
518 class GraphicalProgressIndicator(ProgressIndicator):
519 """Interface to the Blender.Window.DrawProgressBar() method.
522 ProgressIndicator.__init__(self)
524 #self.swirl_chars = ["-", "\\", "|", "/"]
525 # We have to use letters with the same width, for now!
526 # Blender progress bar considers the font widths when
527 # calculating the progress bar width.
528 self.swirl_chars = ["\\", "/"]
529 self.swirl_count = -1
531 def show(self, progress, name):
532 ProgressIndicator.show(self, progress)
534 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
535 swirl_char = self.swirl_chars[self.swirl_count]
537 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
539 # Finally draw the Progress Bar
540 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
541 Window.DrawProgressBar(progress/100.0, progress_text)
544 Window.DrawProgressBar(1, progress_text)
549 # ---------------------------------------------------------------------
551 ## 2D Object representation class
553 # ---------------------------------------------------------------------
555 # TODO: a class to represent the needed properties of a 2D vector image
556 # For now just using a [N]Mesh structure.
559 # ---------------------------------------------------------------------
561 ## Vector Drawing Classes
563 # ---------------------------------------------------------------------
569 A class for printing output in a vectorial format.
571 Given a 2D representation of the 3D scene the class is responsible to
572 write it is a vector format.
574 Every subclasses of VectorWriter must have at last the following public
578 - printCanvas(self, scene,
579 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
582 def __init__(self, fileName):
583 """Set the output file name and other properties"""
585 self.outputFileName = fileName
588 context = Scene.GetCurrent().getRenderingContext()
589 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
593 self.animation = False
600 def open(self, startFrame=1, endFrame=1):
601 if startFrame != endFrame:
602 self.startFrame = startFrame
603 self.endFrame = endFrame
604 self.animation = True
606 self.file = open(self.outputFileName, "w")
607 print "Outputting to: ", self.outputFileName
615 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
616 showHiddenEdges=False):
617 """This is the interface for the needed printing routine.
624 class SVGVectorWriter(VectorWriter):
625 """A concrete class for writing SVG output.
628 def __init__(self, fileName):
629 """Simply call the parent Contructor.
631 VectorWriter.__init__(self, fileName)
638 def open(self, startFrame=1, endFrame=1):
639 """Do some initialization operations.
641 VectorWriter.open(self, startFrame, endFrame)
645 """Do some finalization operation.
649 # remember to call the close method of the parent
650 VectorWriter.close(self)
653 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
654 showHiddenEdges=False):
655 """Convert the scene representation to SVG.
658 Objects = scene.getChildren()
660 context = scene.getRenderingContext()
661 framenumber = context.currentFrame()
664 framestyle = "display:none"
666 framestyle = "display:block"
668 # Assign an id to this group so we can set properties on it using DOM
669 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
670 (framenumber, framestyle) )
675 if(obj.getType() != 'Mesh'):
678 self.file.write("<g id=\"%s\">\n" % obj.getName())
680 mesh = obj.getData(mesh=1)
683 self._printPolygons(mesh)
686 self._printEdges(mesh, showHiddenEdges)
688 self.file.write("</g>\n")
690 self.file.write("</g>\n")
697 def _calcCanvasCoord(self, v):
698 """Convert vertex in scene coordinates to canvas coordinates.
701 pt = Vector([0, 0, 0])
703 mW = float(self.canvasSize[0])/2.0
704 mH = float(self.canvasSize[1])/2.0
706 # rescale to canvas size
707 pt[0] = v.co[0]*mW + mW
708 pt[1] = v.co[1]*mH + mH
711 # For now we want (0,0) in the top-left corner of the canvas.
712 # Mirror and translate along y
714 pt[1] += self.canvasSize[1]
718 def _printHeader(self):
719 """Print SVG header."""
721 self.file.write("<?xml version=\"1.0\"?>\n")
722 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
723 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
724 self.file.write("<svg version=\"1.0\"\n")
725 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
726 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
731 self.file.write("""\n<script type="text/javascript"><![CDATA[
735 /* FIXME: Use 1000 as interval as lower values gives problems */
736 timerID = setInterval("NextFrame()", 1000);
737 globalFrameCounter=%d;
741 currentElement = document.getElementById('frame'+globalFrameCounter)
742 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
749 if (globalFrameCounter > globalEndFrame)
751 clearInterval(timerID)
757 previousElement.style.display="none";
759 currentElement.style.display="block";
760 globalFrameCounter++;
764 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
766 def _printFooter(self):
767 """Print the SVG footer."""
769 self.file.write("\n</svg>\n")
771 def _printPolygons(self, mesh):
772 """Print the selected (visible) polygons.
775 if len(mesh.faces) == 0:
778 self.file.write("<g>\n")
780 for face in mesh.faces:
784 self.file.write("<path d=\"")
786 p = self._calcCanvasCoord(face.verts[0])
787 self.file.write("M %g,%g L " % (p[0], p[1]))
789 for v in face.verts[1:]:
790 p = self._calcCanvasCoord(v)
791 self.file.write("%g,%g " % (p[0], p[1]))
793 # get rid of the last blank space, just cosmetics here.
794 self.file.seek(-1, 1)
795 self.file.write(" z\"\n")
797 # take as face color the first vertex color
800 color = [fcol.r, fcol.g, fcol.b, fcol.a]
802 color = [255, 255, 255, 255]
804 # Convert the color to the #RRGGBB form
805 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
807 # Handle transparent polygons
810 opacity = float(color[3])/255.0
811 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
813 self.file.write("\tstyle=\"fill:" + str_col + ";")
814 self.file.write(opacity_string)
816 # use the stroke property to alleviate the "adjacent edges" problem,
817 # we simulate polygon expansion using borders,
818 # see http://www.antigrain.com/svg/index.html for more info
821 if config.polygons['EXPANSION_TRICK']:
822 str_col = "#000000" # For debug
823 self.file.write(" stroke:%s;\n" % str_col)
824 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
825 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
827 self.file.write("\"/>\n")
829 self.file.write("</g>\n")
831 def _printEdges(self, mesh, showHiddenEdges=False):
832 """Print the wireframe using mesh edges.
835 stroke_width = config.edges['WIDTH']
836 stroke_col = config.edges['COLOR']
838 self.file.write("<g>\n")
842 hidden_stroke_style = ""
845 if showHiddenEdges == False:
848 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
850 p1 = self._calcCanvasCoord(e.v1)
851 p2 = self._calcCanvasCoord(e.v2)
853 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
854 % ( p1[0], p1[1], p2[0], p2[1] ) )
855 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
856 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
857 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
858 self.file.write(hidden_stroke_style)
859 self.file.write("\"/>\n")
861 self.file.write("</g>\n")
864 # ---------------------------------------------------------------------
868 # ---------------------------------------------------------------------
870 # A dictionary to collect different shading style methods
871 shadingStyles = dict()
872 shadingStyles['FLAT'] = None
873 shadingStyles['TOON'] = None
875 # A dictionary to collect different edge style methods
877 edgeStyles['MESH'] = MeshUtils.isMeshEdge
878 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
880 # A dictionary to collect the supported output formats
881 outputWriters = dict()
882 outputWriters['SVG'] = SVGVectorWriter
886 """Render a scene viewed from the active camera.
888 This class is responsible of the rendering process, transformation and
889 projection of the objects in the scene are invoked by the renderer.
891 The rendering is done using the active camera for the current scene.
895 """Make the rendering process only for the current scene by default.
897 We will work on a copy of the scene, to be sure that the current scene do
898 not get modified in any way.
901 # Render the current Scene, this should be a READ-ONLY property
902 self._SCENE = Scene.GetCurrent()
904 # Use the aspect ratio of the scene rendering context
905 context = self._SCENE.getRenderingContext()
907 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
908 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
909 float(context.aspectRatioY())
912 # Render from the currently active camera
913 self.cameraObj = self._SCENE.getCurrentCamera()
915 # Get a projector for this camera.
916 # NOTE: the projector wants object in world coordinates,
917 # so we should remember to apply modelview transformations
918 # _before_ we do projection transformations.
919 self.proj = Projector(self.cameraObj, self.canvasRatio)
921 # Get the list of lighting sources
922 obj_lst = self._SCENE.getChildren()
923 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
925 # When there are no lights we use a default lighting source
926 # that have the same position of the camera
927 if len(self.lights) == 0:
929 lobj = Object.New('Lamp')
930 lobj.loc = self.cameraObj.loc
932 self.lights.append(lobj)
939 def doRendering(self, outputWriter, animation=False):
940 """Render picture or animation and write it out.
943 - a Vector writer object that will be used to output the result.
944 - a flag to tell if we want to render an animation or only the
948 context = self._SCENE.getRenderingContext()
949 origCurrentFrame = context.currentFrame()
951 # Handle the animation case
953 startFrame = origCurrentFrame
954 endFrame = startFrame
957 startFrame = context.startFrame()
958 endFrame = context.endFrame()
959 outputWriter.open(startFrame, endFrame)
961 # Do the rendering process frame by frame
962 print "Start Rendering of %d frames" % (endFrame-startFrame)
963 for f in xrange(startFrame, endFrame+1):
964 print "\n\nFrame: %d" % f
965 context.currentFrame(f)
967 # Use some temporary workspace, a full copy of the scene
968 inputScene = self._SCENE.copy(2)
969 # And Set our camera accordingly
970 self.cameraObj = inputScene.getCurrentCamera()
973 renderedScene = self.doRenderScene(inputScene)
975 print "There was an error! Aborting."
977 print traceback.print_exc()
979 self._SCENE.makeCurrent()
980 Scene.unlink(inputScene)
984 outputWriter.printCanvas(renderedScene,
985 doPrintPolygons = config.polygons['SHOW'],
986 doPrintEdges = config.edges['SHOW'],
987 showHiddenEdges = config.edges['SHOW_HIDDEN'])
989 # delete the rendered scene
990 self._SCENE.makeCurrent()
991 Scene.unlink(renderedScene)
996 context.currentFrame(origCurrentFrame)
999 def doRenderScene(self, workScene):
1000 """Control the rendering process.
1002 Here we control the entire rendering process invoking the operation
1003 needed to transform and project the 3D scene in two dimensions.
1006 # global processing of the scene
1008 self._doSceneClipping(workScene)
1010 self._doConvertGeometricObjsToMesh(workScene)
1012 if config.output['JOIN_OBJECTS']:
1013 self._joinMeshObjectsInScene(workScene)
1015 self._doSceneDepthSorting(workScene)
1017 # Per object activities
1019 Objects = workScene.getChildren()
1020 print "Total Objects: %d" % len(Objects)
1021 for i,obj in enumerate(Objects):
1023 print "Rendering Object: %d" % i
1025 if obj.getType() != 'Mesh':
1026 print "Only Mesh supported! - Skipping type:", obj.getType()
1029 print "Rendering: ", obj.getName()
1031 mesh = obj.getData(mesh=1)
1033 self._doModelingTransformation(mesh, obj.matrix)
1035 self._doBackFaceCulling(mesh)
1037 for f in mesh.faces:
1040 for f in mesh.faces:
1044 self._doLighting(mesh)
1046 # Do "projection" now so we perform further processing
1047 # in Normalized View Coordinates
1048 self._doProjection(mesh, self.proj)
1050 self._doViewFrustumClipping(mesh)
1052 self._doHiddenSurfaceRemoval(mesh)
1054 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
1057 # Update the object data, important! :)
1069 def _getObjPosition(self, obj):
1070 """Return the obj position in World coordinates.
1072 return obj.matrix.translationPart()
1074 def _cameraViewVector(self):
1075 """Get the View Direction form the camera matrix.
1077 return Vector(self.cameraObj.matrix[2]).resize3D()
1082 def _isFaceVisible(self, face):
1083 """Determine if a face of an object is visible from the current camera.
1085 The view vector is calculated from the camera location and one of the
1086 vertices of the face (expressed in World coordinates, after applying
1087 modelview transformations).
1089 After those transformations we determine if a face is visible by
1090 computing the angle between the face normal and the view vector, this
1091 angle has to be between -90 and 90 degrees for the face to be visible.
1092 This corresponds somehow to the dot product between the two, if it
1093 results > 0 then the face is visible.
1095 There is no need to normalize those vectors since we are only interested in
1096 the sign of the cross product and not in the product value.
1098 NOTE: here we assume the face vertices are in WorldCoordinates, so
1099 please transform the object _before_ doing the test.
1102 normal = Vector(face.no)
1103 camPos = self._getObjPosition(self.cameraObj)
1106 # View Vector in orthographics projections is the view Direction of
1108 if self.cameraObj.data.getType() == 1:
1109 view_vect = self._cameraViewVector()
1111 # View vector in perspective projections can be considered as
1112 # the difference between the camera position and one point of
1113 # the face, we choose the farthest point from the camera.
1114 if self.cameraObj.data.getType() == 0:
1115 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
1119 # if d > 0 the face is visible from the camera
1120 d = view_vect * normal
1130 def _doSceneClipping(self, scene):
1131 """Clip whole objects against the View Frustum.
1133 For now clip away only objects according to their center position.
1136 cpos = self._getObjPosition(self.cameraObj)
1137 view_vect = self._cameraViewVector()
1139 near = self.cameraObj.data.clipStart
1140 far = self.cameraObj.data.clipEnd
1142 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
1143 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
1144 fovy = fovy * 360.0/pi
1146 Objects = scene.getChildren()
1148 if o.getType() != 'Mesh': continue;
1150 obj_vect = Vector(cpos) - self._getObjPosition(o)
1152 d = obj_vect*view_vect
1153 theta = AngleBetweenVecs(obj_vect, view_vect)
1155 # if the object is outside the view frustum, clip it away
1156 if (d < near) or (d > far) or (theta > fovy):
1159 def _doConvertGeometricObjsToMesh(self, scene):
1160 """Convert all "geometric" objects to mesh ones.
1162 #geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
1163 geometricObjTypes = ['Mesh', 'Surf', 'Curve']
1165 Objects = scene.getChildren()
1166 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
1169 obj = self._convertToRawMeshObj(obj)
1171 scene.unlink(old_obj)
1174 # XXX Workaround for Text and Curve which have some normals
1175 # inverted when they are converted to Mesh, REMOVE that when
1176 # blender will fix that!!
1177 if old_obj.getType() in ['Curve', 'Text']:
1178 me = obj.getData(mesh=1)
1179 for f in me.faces: f.sel = 1;
1180 for v in me.verts: v.sel = 1;
1187 def _doSceneDepthSorting(self, scene):
1188 """Sort objects in the scene.
1190 The object sorting is done accordingly to the object centers.
1193 c = self._getObjPosition(self.cameraObj)
1195 by_center_pos = (lambda o1, o2:
1196 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
1197 cmp((self._getObjPosition(o1) - Vector(c)).length,
1198 (self._getObjPosition(o2) - Vector(c)).length)
1201 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
1202 # then ob1 goes farther than obj2, useful when obj2 has holes
1205 Objects = scene.getChildren()
1206 Objects.sort(by_center_pos)
1213 def _joinMeshObjectsInScene(self, scene):
1214 """Merge all the Mesh Objects in a scene into a single Mesh Object.
1217 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
1219 # FIXME: Object.join() do not work if the list contains 1 object
1223 mesh = Mesh.New('BigOne')
1224 bigObj = Object.New('Mesh', 'BigOne')
1231 except RuntimeError:
1232 print "\nWarning! - Can't Join Objects\n"
1233 scene.unlink(bigObj)
1236 print "Objects Type error?"
1244 # Per object/mesh methods
1246 def _convertToRawMeshObj(self, object):
1247 """Convert geometry based object to a mesh object.
1249 me = Mesh.New('RawMesh_'+object.name)
1250 me.getFromObject(object.name)
1252 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1255 # If the object has no materials set a default material
1256 if not me.materials:
1257 me.materials = [Material.New()]
1258 #for f in me.faces: f.mat = 0
1260 newObject.setMatrix(object.getMatrix())
1264 def _doModelingTransformation(self, mesh, matrix):
1265 """Transform object coordinates to world coordinates.
1267 This step is done simply applying to the object its tranformation
1268 matrix and recalculating its normals.
1270 # XXX FIXME: blender do not transform normals in the right way when
1271 # there are negative scale values
1272 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1273 print "WARNING: Negative scales, expect incorrect results!"
1275 mesh.transform(matrix, True)
1277 def _doBackFaceCulling(self, mesh):
1278 """Simple Backface Culling routine.
1280 At this level we simply do a visibility test face by face and then
1281 select the vertices belonging to visible faces.
1284 # Select all vertices, so edges can be displayed even if there are no
1286 for v in mesh.verts:
1289 Mesh.Mode(Mesh.SelectModes['FACE'])
1291 for f in mesh.faces:
1293 if self._isFaceVisible(f):
1296 def _doLighting(self, mesh):
1297 """Apply an Illumination and shading model to the object.
1299 The model used is the Phong one, it may be inefficient,
1300 but I'm just learning about rendering and starting from Phong seemed
1301 the most natural way.
1304 # If the mesh has vertex colors already, use them,
1305 # otherwise turn them on and do some calculations
1306 if mesh.vertexColors:
1308 mesh.vertexColors = 1
1310 materials = mesh.materials
1312 camPos = self._getObjPosition(self.cameraObj)
1314 # We do per-face color calculation (FLAT Shading), we can easily turn
1315 # to a per-vertex calculation if we want to implement some shading
1316 # technique. For an example see:
1317 # http://www.miralab.unige.ch/papers/368.pdf
1318 for f in mesh.faces:
1324 mat = materials[f.mat]
1326 # A new default material
1328 mat = Material.New('defMat')
1330 # Check if it is a shadeless material
1331 elif mat.getMode() & Material.Modes['SHADELESS']:
1333 # Convert to a value between 0 and 255
1334 tmp_col = [ int(c * 255.0) for c in I]
1345 # do vertex color calculation
1347 TotDiffSpec = Vector([0.0, 0.0, 0.0])
1349 for l in self.lights:
1351 light_pos = self._getObjPosition(l)
1352 light = light_obj.data
1354 L = Vector(light_pos).normalize()
1356 V = (Vector(camPos) - Vector(f.cent)).normalize()
1358 N = Vector(f.no).normalize()
1360 if config.polygons['SHADING'] == 'TOON':
1361 NL = ShadingUtils.toonShading(N*L)
1365 # Should we use NL instead of (N*L) here?
1366 R = 2 * (N*L) * N - L
1368 Ip = light.getEnergy()
1370 # Diffuse co-efficient
1371 kd = mat.getRef() * Vector(mat.getRGBCol())
1373 kd[i] *= light.col[i]
1375 Idiff = Ip * kd * max(0, NL)
1378 # Specular component
1379 ks = mat.getSpec() * Vector(mat.getSpecCol())
1380 ns = mat.getHardness()
1381 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1383 TotDiffSpec += (Idiff+Ispec)
1387 Iamb = Vector(Blender.World.Get()[0].getAmb())
1390 # Emissive component (convert to a triplet)
1391 ki = Vector([mat.getEmit()]*3)
1393 #I = ki + Iamb + (Idiff + Ispec)
1394 I = ki + (ka * Iamb) + TotDiffSpec
1397 # Set Alpha component
1399 I.append(mat.getAlpha())
1401 # Clamp I values between 0 and 1
1402 I = [ min(c, 1) for c in I]
1403 I = [ max(0, c) for c in I]
1405 # Convert to a value between 0 and 255
1406 tmp_col = [ int(c * 255.0) for c in I]
1414 def _doProjection(self, mesh, projector):
1415 """Apply Viewing and Projection tranformations.
1418 for v in mesh.verts:
1419 p = projector.doProjection(v.co[:])
1424 #mesh.recalcNormals()
1427 # We could reeset Camera matrix, since now
1428 # we are in Normalized Viewing Coordinates,
1429 # but doung that would affect World Coordinate
1430 # processing for other objects
1432 #self.cameraObj.data.type = 1
1433 #self.cameraObj.data.scale = 2.0
1434 #m = Matrix().identity()
1435 #self.cameraObj.setMatrix(m)
1437 def _doViewFrustumClipping(self, mesh):
1438 """Clip faces against the View Frustum.
1442 def __simpleDepthSort(self, mesh):
1443 """Sort faces by the furthest vertex.
1445 This simple mesthod is known also as the painter algorithm, and it
1446 solves HSR correctly only for convex meshes.
1450 # The sorting requires circa n*log(n) steps
1452 progress.setActivity("HSR: Painter", n*log(n))
1455 by_furthest_z = (lambda f1, f2: progress.update() and
1456 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1459 # FIXME: using NMesh to sort faces. We should avoid that!
1460 nmesh = NMesh.GetRaw(mesh.name)
1462 # remember that _higher_ z values mean further points
1463 nmesh.faces.sort(by_furthest_z)
1464 nmesh.faces.reverse()
1468 def __topologicalDepthSort(self, mesh):
1469 """Occlusion based on topological occlusion.
1471 Build the occlusion graph of the mesh,
1472 and then do topological sort on that graph
1476 def __newellDepthSort(self, mesh):
1477 """Newell's depth sorting.
1480 by_furthest_z = (lambda f1, f2:
1481 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2]))
1484 mesh.quadToTriangle(0)
1486 from split import Distance, isOnSegment
1488 def projectionsOverlap(P, Q):
1490 for i in range(0, len(P.v)):
1492 v1 = Vector(P.v[i-1])
1499 for j in range(0, len(Q.v)):
1500 v3 = Vector(Q.v[j-1])
1505 ret = LineIntersect(v1, v2, v3, v4)
1506 # if line v1-v2 and v3-v4 intersect both return
1507 # values are the same.
1508 if ret and ret[0] == ret[1] and isOnSegment(v1, v2,
1509 ret[0], True) and isOnSegment(v3, v4, ret[1], True):
1512 l1 = (ret[0] - v1).length
1513 l2 = (ret[0] - v2).length
1515 l3 = (ret[1] - v3).length
1516 l4 = (ret[1] - v4).length
1518 if (l1 < EPS or l2 < EPS) and (l3 < EPS or l4 < EPS):
1521 debug("Projections OVERLAP!!\n")
1523 " M "+ str(v1[0])+','+str(v1[1]) + ' L ' + str(v2[0])+','+str(v2[1]) + '\n' +
1524 " M "+ str(v3[0])+','+str(v3[1]) + ' L ' + str(v4[0])+','+str(v4[1]) + '\n' +
1526 debug("return: "+ str(ret)+"\n")
1532 from facesplit import facesplit
1534 # FIXME: using NMesh to sort faces. We should avoid that!
1535 nmesh = NMesh.GetRaw(mesh.name)
1537 # remember that _higher_ z values mean further points
1538 nmesh.faces.sort(by_furthest_z)
1539 nmesh.faces.reverse()
1542 # Begin depth sort tests
1544 # use the smooth flag to set marked faces
1545 for f in nmesh.faces:
1548 facelist = nmesh.faces[:]
1555 # The steps are _at_least_ equal to len(facelist), we do not count the
1556 # feces coming out from plitting!!
1557 progress.setActivity("HSR: Newell", len(facelist))
1558 progress.setQuiet(True)
1565 while len(facelist):
1566 print "\n----------------------"
1571 # maplist = facelist
1574 if len(facelist) == 33:
1580 #if P.normal[2] < 0:
1582 pSign = sign(P.normal[2])
1584 # We can discard faces thar are perpendicular to the view
1593 for Q in facelist[1:]:
1595 debug("P.smooth: " + str(P.smooth) + "\n")
1596 debug("Q.smooth: " + str(Q.smooth) + "\n")
1600 #if Q.normal[2] < 0:
1602 qSign = sign(Q.normal[2])
1604 # We need to test only those Qs whose furthest vertex
1605 # is closer to the observer than the closest vertex of P.
1607 zP = [v.co[2] for v in P.v]
1608 zQ = [v.co[2] for v in Q.v]
1609 ZOverlap = min(zP) < max(zQ)
1613 debug("NOT Z OVERLAP!\n")
1615 # If Q is not marked then we can safely print P
1618 debug("met a marked face\n")
1621 # Test 1: X extent overlapping
1622 xP = [v.co[0] for v in P.v]
1623 xQ = [v.co[0] for v in Q.v]
1624 notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
1628 debug("NOT X OVERLAP!\n")
1631 # Test 2: Y extent Overlapping
1632 yP = [v.co[1] for v in P.v]
1633 yQ = [v.co[1] for v in Q.v]
1634 notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
1638 debug("NOT Y OVERLAP!\n")
1642 # Test 3: P vertices are all behind the plane of Q
1645 d = qSign * Distance(Vector(Pi), Q)
1648 pVerticesBehindPlaneQ = (n == len(P))
1650 if pVerticesBehindPlaneQ:
1652 debug("P BEHIND Q!\n")
1656 # Test 4: Q vertices in front of the plane of P
1659 d = pSign * Distance(Vector(Qi), P)
1662 qVerticesInFrontPlaneP = (n == len(Q))
1664 if qVerticesInFrontPlaneP:
1666 debug("Q IN FRONT OF P!\n")
1669 # Test 5: Line Intersections... TODO
1670 # Check if polygons effectively overlap each other, not only
1671 # boundig boxes as done before.
1672 # Since we We are working in normalized projection coordinates
1673 # we kust check if polygons intersect.
1675 if not projectionsOverlap(P, Q):
1677 debug("Projections do not overlap!\n")
1681 # We still do not know if P obscures Q.
1683 # But if Q is marked we do a split trying to resolve a
1684 # difficulty (maybe a visibility cycle).
1687 debug("Possibly a cycle detected!\n")
1688 debug("Split here!!\n")
1689 old_facelist = facelist[:]
1690 facelist = facesplit(P, Q, facelist, nmesh)
1695 # The question now is: Does Q obscure P?
1697 # Test 3bis: Q vertices are all behind the plane of P
1700 d = pSign * Distance(Vector(Qi), P)
1703 qVerticesBehindPlaneP = (n == len(Q))
1705 if qVerticesBehindPlaneP:
1706 debug("\nTest 3bis\n")
1707 debug("Q BEHIND P!\n")
1710 # Test 4bis: P vertices in front of the plane of Q
1713 d = qSign * Distance(Vector(Pi), Q)
1716 pVerticesInFrontPlaneQ = (n == len(P))
1718 if pVerticesInFrontPlaneQ:
1719 debug("\nTest 4bis\n")
1720 debug("P IN FRONT OF Q!\n")
1723 # We don't even know if Q does obscure P, so they should
1724 # intersect each other, split one of them in two parts.
1725 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
1726 debug("\nSimple Intersection?\n")
1727 debug("Test 3bis or 4bis failed\n")
1728 debug("Split here!!2\n")
1730 old_facelist = facelist[:]
1731 facelist = facesplit(P, Q, facelist, nmesh)
1742 facelist.insert(0, Q)
1746 # Make merked faces BLUE. so to see them
1753 debug("Q marked!\n")
1754 print [f.smooth for f in facelist]
1758 if split_done == 0 and face_marked == 0:
1764 #if progress.progressModel.getProgress() == 100:
1783 # for f in facelist:
1784 # if f not in old_facelist:
1794 nmesh.faces = maplist
1796 for f in nmesh.faces:
1801 def _doHiddenSurfaceRemoval(self, mesh):
1802 """Do HSR for the given mesh.
1804 if len(mesh.faces) == 0:
1807 if config.polygons['HSR'] == 'PAINTER':
1808 print "\nUsing the Painter algorithm for HSR."
1809 self.__simpleDepthSort(mesh)
1811 elif config.polygons['HSR'] == 'NEWELL':
1812 print "\nUsing the Newell's algorithm for HSR."
1813 self.__newellDepthSort(mesh)
1816 def _doEdgesStyle(self, mesh, edgestyleSelect):
1817 """Process Mesh Edges accroding to a given selection style.
1819 Examples of algorithms:
1822 given an edge if its adjacent faces have the same normal (that is
1823 they are complanar), than deselect it.
1826 given an edge if one its adjacent faces is frontfacing and the
1827 other is backfacing, than select it, else deselect.
1830 Mesh.Mode(Mesh.SelectModes['EDGE'])
1832 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
1834 for i,edge_faces in enumerate(edge_cache):
1835 mesh.edges[i].sel = 0
1836 if edgestyleSelect(edge_faces):
1837 mesh.edges[i].sel = 1
1840 for e in mesh.edges:
1843 if edgestyleSelect(e, mesh):
1849 # ---------------------------------------------------------------------
1851 ## GUI Class and Main Program
1853 # ---------------------------------------------------------------------
1856 from Blender import BGL, Draw
1857 from Blender.BGL import *
1863 # Output Format menu
1864 output_format = config.output['FORMAT']
1865 default_value = outputWriters.keys().index(output_format)+1
1866 GUI.outFormatMenu = Draw.Create(default_value)
1867 GUI.evtOutFormatMenu = 0
1869 # Animation toggle button
1870 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1871 GUI.evtAnimToggle = 1
1873 # Join Objects toggle button
1874 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1875 GUI.evtJoinObjsToggle = 2
1877 # Render filled polygons
1878 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1880 # Shading Style menu
1881 shading_style = config.polygons['SHADING']
1882 default_value = shadingStyles.keys().index(shading_style)+1
1883 GUI.shadingStyleMenu = Draw.Create(default_value)
1884 GUI.evtShadingStyleMenu = 21
1886 GUI.evtPolygonsToggle = 3
1887 # We hide the config.polygons['EXPANSION_TRICK'], for now
1889 # Render polygon edges
1890 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1891 GUI.evtShowEdgesToggle = 4
1893 # Render hidden edges
1894 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1895 GUI.evtShowHiddenEdgesToggle = 5
1898 edge_style = config.edges['STYLE']
1899 default_value = edgeStyles.keys().index(edge_style)+1
1900 GUI.edgeStyleMenu = Draw.Create(default_value)
1901 GUI.evtEdgeStyleMenu = 6
1904 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
1905 GUI.evtEdgeWidthSlider = 7
1908 c = config.edges['COLOR']
1909 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
1910 GUI.evtEdgeColorPicker = 71
1913 GUI.evtRenderButton = 8
1916 GUI.evtExitButton = 9
1920 # initialize static members
1923 glClear(GL_COLOR_BUFFER_BIT)
1924 glColor3f(0.0, 0.0, 0.0)
1925 glRasterPos2i(10, 350)
1926 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
1928 glRasterPos2i(10, 335)
1929 Draw.Text("Press Q or ESC to quit.")
1931 # Build the output format menu
1932 glRasterPos2i(10, 310)
1933 Draw.Text("Select the output Format:")
1934 outMenuStruct = "Output Format %t"
1935 for t in outputWriters.keys():
1936 outMenuStruct = outMenuStruct + "|%s" % t
1937 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1938 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1941 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1942 10, 260, 160, 18, GUI.animToggle.val,
1943 "Toggle rendering of animations")
1945 # Join Objects toggle
1946 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1947 10, 235, 160, 18, GUI.joinObjsToggle.val,
1948 "Join objects in the rendered file")
1951 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1953 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1956 glRasterPos2i(200, 310)
1957 Draw.Text("Rendering Style:")
1960 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1961 200, 285, 160, 18, GUI.polygonsToggle.val,
1962 "Render filled polygons")
1964 if GUI.polygonsToggle.val == 1:
1966 # Polygon Shading Style
1967 shadingStyleMenuStruct = "Shading Style %t"
1968 for t in shadingStyles.keys():
1969 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
1970 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
1971 200, 260, 160, 18, GUI.shadingStyleMenu.val,
1972 "Choose the shading style")
1976 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1977 200, 235, 160, 18, GUI.showEdgesToggle.val,
1978 "Render polygon edges")
1980 if GUI.showEdgesToggle.val == 1:
1983 edgeStyleMenuStruct = "Edge Style %t"
1984 for t in edgeStyles.keys():
1985 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
1986 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1987 200, 210, 160, 18, GUI.edgeStyleMenu.val,
1988 "Choose the edge style")
1991 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1992 200, 185, 140, 18, GUI.edgeWidthSlider.val,
1993 0.0, 10.0, 0, "Change Edge Width")
1996 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
1997 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
2000 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
2001 GUI.evtShowHiddenEdgesToggle,
2002 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
2003 "Render hidden edges as dashed lines")
2005 glRasterPos2i(10, 160)
2006 Draw.Text("%s (c) 2006" % __author__)
2008 def event(evt, val):
2010 if evt == Draw.ESCKEY or evt == Draw.QKEY:
2017 def button_event(evt):
2019 if evt == GUI.evtExitButton:
2022 elif evt == GUI.evtOutFormatMenu:
2023 i = GUI.outFormatMenu.val - 1
2024 config.output['FORMAT']= outputWriters.keys()[i]
2026 elif evt == GUI.evtAnimToggle:
2027 config.output['ANIMATION'] = bool(GUI.animToggle.val)
2029 elif evt == GUI.evtJoinObjsToggle:
2030 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
2032 elif evt == GUI.evtPolygonsToggle:
2033 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
2035 elif evt == GUI.evtShadingStyleMenu:
2036 i = GUI.shadingStyleMenu.val - 1
2037 config.polygons['SHADING'] = shadingStyles.keys()[i]
2039 elif evt == GUI.evtShowEdgesToggle:
2040 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
2042 elif evt == GUI.evtShowHiddenEdgesToggle:
2043 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
2045 elif evt == GUI.evtEdgeStyleMenu:
2046 i = GUI.edgeStyleMenu.val - 1
2047 config.edges['STYLE'] = edgeStyles.keys()[i]
2049 elif evt == GUI.evtEdgeWidthSlider:
2050 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
2052 elif evt == GUI.evtEdgeColorPicker:
2053 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
2055 elif evt == GUI.evtRenderButton:
2056 label = "Save %s" % config.output['FORMAT']
2057 # Show the File Selector
2059 Blender.Window.FileSelector(vectorize, label, outputfile)
2062 print "Event: %d not handled!" % evt
2069 from pprint import pprint
2071 pprint(config.output)
2072 pprint(config.polygons)
2073 pprint(config.edges)
2075 _init = staticmethod(_init)
2076 draw = staticmethod(draw)
2077 event = staticmethod(event)
2078 button_event = staticmethod(button_event)
2079 conf_debug = staticmethod(conf_debug)
2081 # A wrapper function for the vectorizing process
2082 def vectorize(filename):
2083 """The vectorizing process is as follows:
2085 - Instanciate the writer and the renderer
2090 print "\nERROR: invalid file name!"
2093 from Blender import Window
2094 editmode = Window.EditMode()
2095 if editmode: Window.EditMode(0)
2097 actualWriter = outputWriters[config.output['FORMAT']]
2098 writer = actualWriter(filename)
2100 renderer = Renderer()
2101 renderer.doRendering(writer, config.output['ANIMATION'])
2103 if editmode: Window.EditMode(1)
2105 # We use a global progress Indicator Object
2109 if __name__ == "__main__":
2114 basename = Blender.sys.basename(Blender.Get('filename'))
2116 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
2118 if Blender.mode == 'background':
2119 progress = ConsoleProgressIndicator()
2120 vectorize(outputfile)
2122 progress = GraphicalProgressIndicator()
2123 Draw.Register(GUI.draw, GUI.event, GUI.button_event)