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.
79 # * Initial SWF output support
80 # * Fixed a bug in the animation code, now the projection matrix is
81 # recalculated at each frame!
83 # ---------------------------------------------------------------------
86 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
87 from Blender.Mathutils import *
94 # We use a global progress Indicator Object
98 # Some global settings
102 polygons['SHOW'] = True
103 polygons['SHADING'] = 'FLAT'
104 #polygons['HSR'] = 'PAINTER' # 'PAINTER' or 'NEWELL'
105 polygons['HSR'] = 'PAINTER'
106 # Hidden to the user for now
107 polygons['EXPANSION_TRICK'] = True
109 polygons['TOON_LEVELS'] = 2
112 edges['SHOW'] = False
113 edges['SHOW_HIDDEN'] = False
114 edges['STYLE'] = 'MESH' # or SILHOUETTE
115 edges['STYLE'] = 'SILHOUETTE'
117 edges['COLOR'] = [0, 0, 0]
120 output['FORMAT'] = 'SVG'
121 output['FORMAT'] = 'SWF'
122 output['ANIMATION'] = True
123 output['JOIN_OBJECTS'] = True
138 # ---------------------------------------------------------------------
140 ## Mesh Utility class
142 # ---------------------------------------------------------------------
145 def buildEdgeFaceUsersCache(me):
147 Takes a mesh and returns a list aligned with the meshes edges.
148 Each item is a list of the faces that use the edge
149 would be the equiv for having ed.face_users as a property
151 Taken from .blender/scripts/bpymodules/BPyMesh.py,
152 thanks to ideasman_42.
155 def sorted_edge_indicies(ed):
163 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
165 fvi= [v.index for v in f.v]# face vert idx's
166 for i in xrange(len(f)):
173 face_edges_dict[i1,i2][1].append(f)
175 face_edges= [None] * len(me.edges)
176 for ed_index, ed_faces in face_edges_dict.itervalues():
177 face_edges[ed_index]= ed_faces
181 def isMeshEdge(adjacent_faces):
184 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
185 Note: if the edge has no adjacent faces we want to show it as well,
186 useful for "edge only" portion of objects.
189 if len(adjacent_faces) == 0:
192 selected_faces = [f for f in adjacent_faces if f.sel]
194 if len(selected_faces) != 0:
199 def isSilhouetteEdge(adjacent_faces):
200 """Silhuette selection rule.
202 An edge is a silhuette edge if it is shared by two faces with
203 different selection status or if it is a boundary edge of a selected
207 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
208 (len(adjacent_faces) == 2 and
209 adjacent_faces[0].sel != adjacent_faces[1].sel)
215 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
216 isMeshEdge = staticmethod(isMeshEdge)
217 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
220 # ---------------------------------------------------------------------
222 ## Shading Utility class
224 # ---------------------------------------------------------------------
229 def toonShadingMapSetup():
230 levels = config.polygons['TOON_LEVELS']
232 texels = 2*levels - 1
233 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
239 shademap = ShadingUtils.shademap
242 shademap = ShadingUtils.toonShadingMapSetup()
245 for i in xrange(0, len(shademap)-1):
246 pivot = (shademap[i]+shademap[i+1])/2.0
251 if v < shademap[i+1]:
256 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
257 toonShading = staticmethod(toonShading)
260 # ---------------------------------------------------------------------
262 ## Projections classes
264 # ---------------------------------------------------------------------
267 """Calculate the projection of an object given the camera.
269 A projector is useful to so some per-object transformation to obtain the
270 projection of an object given the camera.
272 The main method is #doProjection# see the method description for the
276 def __init__(self, cameraObj, canvasRatio):
277 """Calculate the projection matrix.
279 The projection matrix depends, in this case, on the camera settings.
280 TAKE CARE: This projector expects vertices in World Coordinates!
283 camera = cameraObj.getData()
285 aspect = float(canvasRatio[0])/float(canvasRatio[1])
286 near = camera.clipStart
289 scale = float(camera.scale)
291 fovy = atan(0.5/aspect/(camera.lens/32))
292 fovy = fovy * 360.0/pi
294 # What projection do we want?
296 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
297 elif camera.type == 1:
298 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
300 # View transformation
301 cam = Matrix(cameraObj.getInverseMatrix())
306 self.projectionMatrix = mP
312 def doProjection(self, v):
313 """Project the point on the view plane.
315 Given a vertex calculate the projection using the current projection
319 # Note that we have to work on the vertex using homogeneous coordinates
320 # From blender 2.42+ we don't need to resize the vector to be 4d
321 # when applying a 4x4 matrix, but we do that anyway since we need the
322 # 4th coordinate later
323 p = self.projectionMatrix * Vector(v).resize4D()
325 # Perspective division
342 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
343 """Return a perspective projection matrix.
346 top = near * tan(fovy * pi / 360.0)
350 x = (2.0 * near) / (right-left)
351 y = (2.0 * near) / (top-bottom)
352 a = (right+left) / (right-left)
353 b = (top+bottom) / (top - bottom)
354 c = - ((far+near) / (far-near))
355 d = - ((2*far*near)/(far-near))
361 [0.0, 0.0, -1.0, 0.0])
365 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
366 """Return an orthogonal projection matrix.
369 # The 11 in the formula was found emiprically
370 top = near * tan(fovy * pi / 360.0) * (scale * 11)
372 left = bottom * aspect
377 tx = -((right+left)/rl)
378 ty = -((top+bottom)/tb)
382 [2.0/rl, 0.0, 0.0, tx],
383 [0.0, 2.0/tb, 0.0, ty],
384 [0.0, 0.0, 2.0/fn, tz],
385 [0.0, 0.0, 0.0, 1.0])
390 # ---------------------------------------------------------------------
392 ## Progress Indicator
394 # ---------------------------------------------------------------------
397 """A model for a progress indicator.
399 Do the progress calculation calculation and
400 the view independent stuff of a progress indicator.
402 def __init__(self, steps=0):
408 def setSteps(self, steps):
409 """Set the number of steps of the activity wich we want to track.
416 def setName(self, name):
417 """Set the name of the activity wich we want to track.
424 def getProgress(self):
432 """Update the model, call this method when one step is completed.
434 if self.progress == 100:
438 self.progress = ( float(self.completed) / float(self.steps) ) * 100
439 self.progress = int(self.progress)
444 class ProgressIndicator:
445 """An abstraction of a View for the Progress Model
449 # Use a refresh rate so we do not show the progress at
450 # every update, but every 'self.refresh_rate' times.
451 self.refresh_rate = 10
452 self.shows_counter = 0
456 self.progressModel = None
458 def setQuiet(self, value):
461 def setActivity(self, name, steps):
462 """Initialize the Model.
464 In a future version (with subactivities-progress support) this method
465 could only set the current activity.
467 self.progressModel = Progress()
468 self.progressModel.setName(name)
469 self.progressModel.setSteps(steps)
471 def getActivity(self):
472 return self.progressModel
475 """Update the model and show the actual progress.
477 assert(self.progressModel)
479 if self.progressModel.update():
483 self.show(self.progressModel.getProgress(),
484 self.progressModel.getName())
486 # We return always True here so we can call the update() method also
487 # from lambda funcs (putting the call in logical AND with other ops)
490 def show(self, progress, name=""):
491 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
492 if self.shows_counter != 0:
496 self.shows_counter = -1
499 class ConsoleProgressIndicator(ProgressIndicator):
500 """Show a progress bar on stderr, a la wget.
503 ProgressIndicator.__init__(self)
505 self.swirl_chars = ["-", "\\", "|", "/"]
506 self.swirl_count = -1
508 def show(self, progress, name):
509 ProgressIndicator.show(self, progress, name)
512 bar_progress = int( (progress/100.0) * bar_length )
513 bar = ("=" * bar_progress).ljust(bar_length)
515 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
516 swirl_char = self.swirl_chars[self.swirl_count]
518 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
520 sys.stderr.write(progress_bar+"\r")
522 sys.stderr.write("\n")
525 class GraphicalProgressIndicator(ProgressIndicator):
526 """Interface to the Blender.Window.DrawProgressBar() method.
529 ProgressIndicator.__init__(self)
531 #self.swirl_chars = ["-", "\\", "|", "/"]
532 # We have to use letters with the same width, for now!
533 # Blender progress bar considers the font widths when
534 # calculating the progress bar width.
535 self.swirl_chars = ["\\", "/"]
536 self.swirl_count = -1
538 def show(self, progress, name):
539 ProgressIndicator.show(self, progress)
541 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
542 swirl_char = self.swirl_chars[self.swirl_count]
544 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
546 # Finally draw the Progress Bar
547 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
548 Window.DrawProgressBar(progress/100.0, progress_text)
551 Window.DrawProgressBar(1, progress_text)
556 # ---------------------------------------------------------------------
558 ## 2D Object representation class
560 # ---------------------------------------------------------------------
562 # TODO: a class to represent the needed properties of a 2D vector image
563 # For now just using a [N]Mesh structure.
566 # ---------------------------------------------------------------------
568 ## Vector Drawing Classes
570 # ---------------------------------------------------------------------
576 A class for printing output in a vectorial format.
578 Given a 2D representation of the 3D scene the class is responsible to
579 write it is a vector format.
581 Every subclasses of VectorWriter must have at last the following public
585 - printCanvas(self, scene,
586 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
589 def __init__(self, fileName):
590 """Set the output file name and other properties"""
592 self.outputFileName = fileName
595 context = Scene.GetCurrent().getRenderingContext()
596 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
600 self.animation = False
607 def open(self, startFrame=1, endFrame=1):
608 if startFrame != endFrame:
609 self.startFrame = startFrame
610 self.endFrame = endFrame
611 self.animation = True
613 self.file = open(self.outputFileName, "w")
614 print "Outputting to: ", self.outputFileName
623 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
624 showHiddenEdges=False):
625 """This is the interface for the needed printing routine.
632 class SVGVectorWriter(VectorWriter):
633 """A concrete class for writing SVG output.
636 def __init__(self, fileName):
637 """Simply call the parent Contructor.
639 VectorWriter.__init__(self, fileName)
646 def open(self, startFrame=1, endFrame=1):
647 """Do some initialization operations.
649 VectorWriter.open(self, startFrame, endFrame)
653 """Do some finalization operation.
657 # remember to call the close method of the parent
658 VectorWriter.close(self)
661 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
662 showHiddenEdges=False):
663 """Convert the scene representation to SVG.
666 Objects = scene.getChildren()
668 context = scene.getRenderingContext()
669 framenumber = context.currentFrame()
672 framestyle = "display:none"
674 framestyle = "display:block"
676 # Assign an id to this group so we can set properties on it using DOM
677 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
678 (framenumber, framestyle) )
683 if(obj.getType() != 'Mesh'):
686 self.file.write("<g id=\"%s\">\n" % obj.getName())
688 mesh = obj.getData(mesh=1)
691 self._printPolygons(mesh)
694 self._printEdges(mesh, showHiddenEdges)
696 self.file.write("</g>\n")
698 self.file.write("</g>\n")
705 def _calcCanvasCoord(self, v):
706 """Convert vertex in scene coordinates to canvas coordinates.
709 pt = Vector([0, 0, 0])
711 mW = float(self.canvasSize[0])/2.0
712 mH = float(self.canvasSize[1])/2.0
714 # rescale to canvas size
715 pt[0] = v.co[0]*mW + mW
716 pt[1] = v.co[1]*mH + mH
719 # For now we want (0,0) in the top-left corner of the canvas.
720 # Mirror and translate along y
722 pt[1] += self.canvasSize[1]
726 def _printHeader(self):
727 """Print SVG header."""
729 self.file.write("<?xml version=\"1.0\"?>\n")
730 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
731 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
732 self.file.write("<svg version=\"1.0\"\n")
733 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
734 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
739 self.file.write("""\n<script type="text/javascript"><![CDATA[
743 /* FIXME: Use 1000 as interval as lower values gives problems */
744 timerID = setInterval("NextFrame()", 1000);
745 globalFrameCounter=%d;
749 currentElement = document.getElementById('frame'+globalFrameCounter)
750 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
757 if (globalFrameCounter > globalEndFrame)
759 clearInterval(timerID)
765 previousElement.style.display="none";
767 currentElement.style.display="block";
768 globalFrameCounter++;
772 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
774 def _printFooter(self):
775 """Print the SVG footer."""
777 self.file.write("\n</svg>\n")
779 def _printPolygons(self, mesh):
780 """Print the selected (visible) polygons.
783 if len(mesh.faces) == 0:
786 self.file.write("<g>\n")
788 for face in mesh.faces:
792 self.file.write("<path d=\"")
794 p = self._calcCanvasCoord(face.verts[0])
795 self.file.write("M %g,%g L " % (p[0], p[1]))
797 for v in face.verts[1:]:
798 p = self._calcCanvasCoord(v)
799 self.file.write("%g,%g " % (p[0], p[1]))
801 # get rid of the last blank space, just cosmetics here.
802 self.file.seek(-1, 1)
803 self.file.write(" z\"\n")
805 # take as face color the first vertex color
808 color = [fcol.r, fcol.g, fcol.b, fcol.a]
810 color = [255, 255, 255, 255]
812 # Convert the color to the #RRGGBB form
813 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
815 # Handle transparent polygons
818 opacity = float(color[3])/255.0
819 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
820 #opacity_string = "opacity: %g;" % (opacity)
822 self.file.write("\tstyle=\"fill:" + str_col + ";")
823 self.file.write(opacity_string)
825 # use the stroke property to alleviate the "adjacent edges" problem,
826 # we simulate polygon expansion using borders,
827 # see http://www.antigrain.com/svg/index.html for more info
830 # EXPANSION TRICK is not that useful where there is transparency
831 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
832 # str_col = "#000000" # For debug
833 self.file.write(" stroke:%s;\n" % str_col)
834 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
835 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
837 self.file.write("\"/>\n")
839 self.file.write("</g>\n")
841 def _printEdges(self, mesh, showHiddenEdges=False):
842 """Print the wireframe using mesh edges.
845 stroke_width = config.edges['WIDTH']
846 stroke_col = config.edges['COLOR']
848 self.file.write("<g>\n")
852 hidden_stroke_style = ""
855 if showHiddenEdges == False:
858 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
860 p1 = self._calcCanvasCoord(e.v1)
861 p2 = self._calcCanvasCoord(e.v2)
863 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
864 % ( p1[0], p1[1], p2[0], p2[1] ) )
865 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
866 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
867 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
868 self.file.write(hidden_stroke_style)
869 self.file.write("\"/>\n")
871 self.file.write("</g>\n")
878 class SWFVectorWriter(VectorWriter):
879 """A concrete class for writing SWF output.
882 def __init__(self, fileName):
883 """Simply call the parent Contructor.
885 VectorWriter.__init__(self, fileName)
895 def open(self, startFrame=1, endFrame=1):
896 """Do some initialization operations.
898 VectorWriter.open(self, startFrame, endFrame)
899 self.movie = SWFMovie()
900 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
902 self.movie.setRate(25)
903 numframes = endFrame - startFrame + 1
904 self.movie.setFrames(numframes)
907 """Do some finalization operation.
909 self.movie.save(self.outputFileName)
911 # remember to call the close method of the parent
912 VectorWriter.close(self)
914 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
915 showHiddenEdges=False):
916 """Convert the scene representation to SVG.
918 context = scene.getRenderingContext()
919 framenumber = context.currentFrame()
921 Objects = scene.getChildren()
924 self.movie.remove(self.sprite)
930 if(obj.getType() != 'Mesh'):
933 mesh = obj.getData(mesh=1)
936 self._printPolygons(mesh, sprite)
939 self._printEdges(mesh, sprite, showHiddenEdges)
942 i = self.movie.add(sprite)
943 # Remove the instance the next time
946 self.movie.nextFrame()
953 def _calcCanvasCoord(self, v):
954 """Convert vertex in scene coordinates to canvas coordinates.
957 pt = Vector([0, 0, 0])
959 mW = float(self.canvasSize[0])/2.0
960 mH = float(self.canvasSize[1])/2.0
962 # rescale to canvas size
963 pt[0] = v.co[0]*mW + mW
964 pt[1] = v.co[1]*mH + mH
967 # For now we want (0,0) in the top-left corner of the canvas.
968 # Mirror and translate along y
970 pt[1] += self.canvasSize[1]
974 def _printPolygons(self, mesh, sprite):
975 """Print the selected (visible) polygons.
978 if len(mesh.faces) == 0:
981 for face in mesh.faces:
987 color = [fcol.r, fcol.g, fcol.b, fcol.a]
989 color = [255, 255, 255, 255]
992 f = s.addFill(color[0], color[1], color[2], color[3])
995 # The starting point of the shape
996 p0 = self._calcCanvasCoord(face.verts[0])
997 s.movePenTo(p0[0], p0[1])
1000 for v in face.verts[1:]:
1001 p = self._calcCanvasCoord(v)
1002 s.drawLineTo(p[0], p[1])
1005 s.drawLineTo(p0[0], p0[1])
1011 # use the stroke property to alleviate the "adjacent edges" problem,
1012 # we simulate polygon expansion using borders,
1013 # see http://www.antigrain.com/svg/index.html for more info
1016 # EXPANSION TRICK is not that useful where there is transparency
1017 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1018 # str_col = "#000000" # For debug
1019 self.file.write(" stroke:%s;\n" % str_col)
1020 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1021 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1025 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1026 """Print the wireframe using mesh edges.
1029 stroke_width = config.edges['WIDTH']
1030 stroke_col = config.edges['COLOR']
1034 for e in mesh.edges:
1036 #Next, we set the line width and color for our shape.
1037 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1041 if showHiddenEdges == False:
1044 # SWF does not support dashed lines natively, so -for now-
1045 # draw hidden lines thinner and half-trasparent
1046 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1049 p1 = self._calcCanvasCoord(e.v1)
1050 p2 = self._calcCanvasCoord(e.v2)
1052 # FIXME: this is just a qorkaround, remove that after the
1053 # implementation of propoer Viewport clipping
1054 if abs(p1[0]) < 3000 and abs(p2[0]) < 3000 and abs(p1[1]) < 3000 and abs(p1[2]) < 3000:
1055 s.movePenTo(p1[0], p1[1])
1056 s.drawLineTo(p2[0], p2[1])
1064 # ---------------------------------------------------------------------
1066 ## Rendering Classes
1068 # ---------------------------------------------------------------------
1070 # A dictionary to collect different shading style methods
1071 shadingStyles = dict()
1072 shadingStyles['FLAT'] = None
1073 shadingStyles['TOON'] = None
1075 # A dictionary to collect different edge style methods
1077 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1078 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1080 # A dictionary to collect the supported output formats
1081 outputWriters = dict()
1082 outputWriters['SVG'] = SVGVectorWriter
1083 outputWriters['SWF'] = SWFVectorWriter
1087 """Render a scene viewed from the active camera.
1089 This class is responsible of the rendering process, transformation and
1090 projection of the objects in the scene are invoked by the renderer.
1092 The rendering is done using the active camera for the current scene.
1096 """Make the rendering process only for the current scene by default.
1098 We will work on a copy of the scene, to be sure that the current scene do
1099 not get modified in any way.
1102 # Render the current Scene, this should be a READ-ONLY property
1103 self._SCENE = Scene.GetCurrent()
1105 # Use the aspect ratio of the scene rendering context
1106 context = self._SCENE.getRenderingContext()
1108 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1109 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1110 float(context.aspectRatioY())
1113 # Render from the currently active camera
1114 self.cameraObj = self._SCENE.getCurrentCamera()
1116 # Get the list of lighting sources
1117 obj_lst = self._SCENE.getChildren()
1118 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
1120 # When there are no lights we use a default lighting source
1121 # that have the same position of the camera
1122 if len(self.lights) == 0:
1123 l = Lamp.New('Lamp')
1124 lobj = Object.New('Lamp')
1125 lobj.loc = self.cameraObj.loc
1127 self.lights.append(lobj)
1134 def doRendering(self, outputWriter, animation=False):
1135 """Render picture or animation and write it out.
1138 - a Vector writer object that will be used to output the result.
1139 - a flag to tell if we want to render an animation or only the
1143 context = self._SCENE.getRenderingContext()
1144 origCurrentFrame = context.currentFrame()
1146 # Handle the animation case
1148 startFrame = origCurrentFrame
1149 endFrame = startFrame
1152 startFrame = context.startFrame()
1153 endFrame = context.endFrame()
1154 outputWriter.open(startFrame, endFrame)
1156 # Do the rendering process frame by frame
1157 print "Start Rendering of %d frames" % (endFrame-startFrame)
1158 for f in xrange(startFrame, endFrame+1):
1159 print "\n\nFrame: %d" % f
1160 context.currentFrame(f)
1162 # Use some temporary workspace, a full copy of the scene
1163 inputScene = self._SCENE.copy(2)
1164 # And Set our camera accordingly
1165 self.cameraObj = inputScene.getCurrentCamera()
1167 # Get a projector for this camera.
1168 # NOTE: the projector wants object in world coordinates,
1169 # so we should remember to apply modelview transformations
1170 # _before_ we do projection transformations.
1171 self.proj = Projector(self.cameraObj, self.canvasRatio)
1174 renderedScene = self.doRenderScene(inputScene)
1176 print "There was an error! Aborting."
1178 print traceback.print_exc()
1180 self._SCENE.makeCurrent()
1181 Scene.unlink(inputScene)
1185 outputWriter.printCanvas(renderedScene,
1186 doPrintPolygons = config.polygons['SHOW'],
1187 doPrintEdges = config.edges['SHOW'],
1188 showHiddenEdges = config.edges['SHOW_HIDDEN'])
1190 # delete the rendered scene
1191 self._SCENE.makeCurrent()
1192 Scene.unlink(renderedScene)
1195 outputWriter.close()
1197 context.currentFrame(origCurrentFrame)
1200 def doRenderScene(self, workScene):
1201 """Control the rendering process.
1203 Here we control the entire rendering process invoking the operation
1204 needed to transform and project the 3D scene in two dimensions.
1207 # global processing of the scene
1209 self._doSceneClipping(workScene)
1211 self._doConvertGeometricObjsToMesh(workScene)
1213 if config.output['JOIN_OBJECTS']:
1214 self._joinMeshObjectsInScene(workScene)
1216 self._doSceneDepthSorting(workScene)
1218 # Per object activities
1220 Objects = workScene.getChildren()
1221 print "Total Objects: %d" % len(Objects)
1222 for i,obj in enumerate(Objects):
1224 print "Rendering Object: %d" % i
1226 if obj.getType() != 'Mesh':
1227 print "Only Mesh supported! - Skipping type:", obj.getType()
1230 print "Rendering: ", obj.getName()
1232 mesh = obj.getData(mesh=1)
1234 self._doModelingTransformation(mesh, obj.matrix)
1236 self._doBackFaceCulling(mesh)
1239 # When doing HSR with NEWELL we may want to flip all normals
1241 if config.polygons['HSR'] == "NEWELL":
1242 for f in mesh.faces:
1245 for f in mesh.faces:
1248 self._doLighting(mesh)
1250 # Do "projection" now so we perform further processing
1251 # in Normalized View Coordinates
1252 self._doProjection(mesh, self.proj)
1254 self._doViewFrustumClipping(mesh)
1256 self._doHiddenSurfaceRemoval(mesh)
1258 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
1260 # Update the object data, important! :)
1272 def _getObjPosition(self, obj):
1273 """Return the obj position in World coordinates.
1275 return obj.matrix.translationPart()
1277 def _cameraViewVector(self):
1278 """Get the View Direction form the camera matrix.
1280 return Vector(self.cameraObj.matrix[2]).resize3D()
1285 def _isFaceVisible(self, face):
1286 """Determine if a face of an object is visible from the current camera.
1288 The view vector is calculated from the camera location and one of the
1289 vertices of the face (expressed in World coordinates, after applying
1290 modelview transformations).
1292 After those transformations we determine if a face is visible by
1293 computing the angle between the face normal and the view vector, this
1294 angle has to be between -90 and 90 degrees for the face to be visible.
1295 This corresponds somehow to the dot product between the two, if it
1296 results > 0 then the face is visible.
1298 There is no need to normalize those vectors since we are only interested in
1299 the sign of the cross product and not in the product value.
1301 NOTE: here we assume the face vertices are in WorldCoordinates, so
1302 please transform the object _before_ doing the test.
1305 normal = Vector(face.no)
1306 camPos = self._getObjPosition(self.cameraObj)
1309 # View Vector in orthographics projections is the view Direction of
1311 if self.cameraObj.data.getType() == 1:
1312 view_vect = self._cameraViewVector()
1314 # View vector in perspective projections can be considered as
1315 # the difference between the camera position and one point of
1316 # the face, we choose the farthest point from the camera.
1317 if self.cameraObj.data.getType() == 0:
1318 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
1322 # if d > 0 the face is visible from the camera
1323 d = view_vect * normal
1333 def _doSceneClipping(self, scene):
1334 """Clip whole objects against the View Frustum.
1336 For now clip away only objects according to their center position.
1339 cpos = self._getObjPosition(self.cameraObj)
1340 view_vect = self._cameraViewVector()
1342 near = self.cameraObj.data.clipStart
1343 far = self.cameraObj.data.clipEnd
1345 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
1346 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
1347 fovy = fovy * 360.0/pi
1349 Objects = scene.getChildren()
1351 if o.getType() != 'Mesh': continue;
1353 obj_vect = Vector(cpos) - self._getObjPosition(o)
1355 d = obj_vect*view_vect
1356 theta = AngleBetweenVecs(obj_vect, view_vect)
1358 # if the object is outside the view frustum, clip it away
1359 if (d < near) or (d > far) or (theta > fovy):
1362 def _doConvertGeometricObjsToMesh(self, scene):
1363 """Convert all "geometric" objects to mesh ones.
1365 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
1366 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
1368 Objects = scene.getChildren()
1369 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
1372 obj = self._convertToRawMeshObj(obj)
1374 scene.unlink(old_obj)
1377 # XXX Workaround for Text and Curve which have some normals
1378 # inverted when they are converted to Mesh, REMOVE that when
1379 # blender will fix that!!
1380 if old_obj.getType() in ['Curve', 'Text']:
1381 me = obj.getData(mesh=1)
1382 for f in me.faces: f.sel = 1;
1383 for v in me.verts: v.sel = 1;
1390 def _doSceneDepthSorting(self, scene):
1391 """Sort objects in the scene.
1393 The object sorting is done accordingly to the object centers.
1396 c = self._getObjPosition(self.cameraObj)
1398 by_center_pos = (lambda o1, o2:
1399 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
1400 cmp((self._getObjPosition(o1) - Vector(c)).length,
1401 (self._getObjPosition(o2) - Vector(c)).length)
1404 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
1405 # then ob1 goes farther than obj2, useful when obj2 has holes
1408 Objects = scene.getChildren()
1409 Objects.sort(by_center_pos)
1416 def _joinMeshObjectsInScene(self, scene):
1417 """Merge all the Mesh Objects in a scene into a single Mesh Object.
1420 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
1422 # FIXME: Object.join() do not work if the list contains 1 object
1426 mesh = Mesh.New('BigOne')
1427 bigObj = Object.New('Mesh', 'BigOne')
1434 except RuntimeError:
1435 print "\nWarning! - Can't Join Objects\n"
1436 scene.unlink(bigObj)
1439 print "Objects Type error?"
1447 # Per object/mesh methods
1449 def _convertToRawMeshObj(self, object):
1450 """Convert geometry based object to a mesh object.
1452 me = Mesh.New('RawMesh_'+object.name)
1453 me.getFromObject(object.name)
1455 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1458 # If the object has no materials set a default material
1459 if not me.materials:
1460 me.materials = [Material.New()]
1461 #for f in me.faces: f.mat = 0
1463 newObject.setMatrix(object.getMatrix())
1467 def _doModelingTransformation(self, mesh, matrix):
1468 """Transform object coordinates to world coordinates.
1470 This step is done simply applying to the object its tranformation
1471 matrix and recalculating its normals.
1473 # XXX FIXME: blender do not transform normals in the right way when
1474 # there are negative scale values
1475 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1476 print "WARNING: Negative scales, expect incorrect results!"
1478 mesh.transform(matrix, True)
1480 def _doBackFaceCulling(self, mesh):
1481 """Simple Backface Culling routine.
1483 At this level we simply do a visibility test face by face and then
1484 select the vertices belonging to visible faces.
1487 # Select all vertices, so edges can be displayed even if there are no
1489 for v in mesh.verts:
1492 Mesh.Mode(Mesh.SelectModes['FACE'])
1494 for f in mesh.faces:
1496 if self._isFaceVisible(f):
1499 def _doLighting(self, mesh):
1500 """Apply an Illumination and shading model to the object.
1502 The model used is the Phong one, it may be inefficient,
1503 but I'm just learning about rendering and starting from Phong seemed
1504 the most natural way.
1507 # If the mesh has vertex colors already, use them,
1508 # otherwise turn them on and do some calculations
1509 if mesh.vertexColors:
1511 mesh.vertexColors = 1
1513 materials = mesh.materials
1515 camPos = self._getObjPosition(self.cameraObj)
1517 # We do per-face color calculation (FLAT Shading), we can easily turn
1518 # to a per-vertex calculation if we want to implement some shading
1519 # technique. For an example see:
1520 # http://www.miralab.unige.ch/papers/368.pdf
1521 for f in mesh.faces:
1527 mat = materials[f.mat]
1529 # A new default material
1531 mat = Material.New('defMat')
1533 # Check if it is a shadeless material
1534 elif mat.getMode() & Material.Modes['SHADELESS']:
1536 # Convert to a value between 0 and 255
1537 tmp_col = [ int(c * 255.0) for c in I]
1548 # do vertex color calculation
1550 TotDiffSpec = Vector([0.0, 0.0, 0.0])
1552 for l in self.lights:
1554 light_pos = self._getObjPosition(l)
1555 light = light_obj.getData()
1557 L = Vector(light_pos).normalize()
1559 V = (Vector(camPos) - Vector(f.cent)).normalize()
1561 N = Vector(f.no).normalize()
1563 if config.polygons['SHADING'] == 'TOON':
1564 NL = ShadingUtils.toonShading(N*L)
1568 # Should we use NL instead of (N*L) here?
1569 R = 2 * (N*L) * N - L
1571 Ip = light.getEnergy()
1573 # Diffuse co-efficient
1574 kd = mat.getRef() * Vector(mat.getRGBCol())
1576 kd[i] *= light.col[i]
1578 Idiff = Ip * kd * max(0, NL)
1581 # Specular component
1582 ks = mat.getSpec() * Vector(mat.getSpecCol())
1583 ns = mat.getHardness()
1584 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1586 TotDiffSpec += (Idiff+Ispec)
1590 Iamb = Vector(Blender.World.Get()[0].getAmb())
1593 # Emissive component (convert to a triplet)
1594 ki = Vector([mat.getEmit()]*3)
1596 #I = ki + Iamb + (Idiff + Ispec)
1597 I = ki + (ka * Iamb) + TotDiffSpec
1600 # Set Alpha component
1602 I.append(mat.getAlpha())
1604 # Clamp I values between 0 and 1
1605 I = [ min(c, 1) for c in I]
1606 I = [ max(0, c) for c in I]
1608 # Convert to a value between 0 and 255
1609 tmp_col = [ int(c * 255.0) for c in I]
1617 def _doProjection(self, mesh, projector):
1618 """Apply Viewing and Projection tranformations.
1621 for v in mesh.verts:
1622 p = projector.doProjection(v.co[:])
1627 #mesh.recalcNormals()
1630 # We could reeset Camera matrix, since now
1631 # we are in Normalized Viewing Coordinates,
1632 # but doung that would affect World Coordinate
1633 # processing for other objects
1635 #self.cameraObj.data.type = 1
1636 #self.cameraObj.data.scale = 2.0
1637 #m = Matrix().identity()
1638 #self.cameraObj.setMatrix(m)
1640 def _doViewFrustumClipping(self, mesh):
1641 """Clip faces against the View Frustum.
1645 def __simpleDepthSort(self, mesh):
1646 """Sort faces by the furthest vertex.
1648 This simple mesthod is known also as the painter algorithm, and it
1649 solves HSR correctly only for convex meshes.
1654 # The sorting requires circa n*log(n) steps
1656 progress.setActivity("HSR: Painter", n*log(n))
1658 by_furthest_z = (lambda f1, f2: progress.update() and
1659 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
1662 # FIXME: using NMesh to sort faces. We should avoid that!
1663 nmesh = NMesh.GetRaw(mesh.name)
1665 # remember that _higher_ z values mean further points
1666 nmesh.faces.sort(by_furthest_z)
1667 nmesh.faces.reverse()
1672 def __newellDepthSort(self, mesh):
1673 """Newell's depth sorting.
1680 # Find non planar quads and convert them to triangle
1681 #for f in mesh.faces:
1683 # if is_nonplanar_quad(f.v):
1684 # print "NON QUAD??"
1688 # Now reselect all faces
1689 for f in mesh.faces:
1691 mesh.quadToTriangle()
1693 # FIXME: using NMesh to sort faces. We should avoid that!
1694 nmesh = NMesh.GetRaw(mesh.name)
1696 # remember that _higher_ z values mean further points
1697 nmesh.faces.sort(by_furthest_z)
1698 nmesh.faces.reverse()
1700 # Begin depth sort tests
1702 # use the smooth flag to set marked faces
1703 for f in nmesh.faces:
1706 facelist = nmesh.faces[:]
1710 # The steps are _at_least_ equal to len(facelist), we do not count the
1711 # feces coming out from splitting!!
1712 progress.setActivity("HSR: Newell", len(facelist))
1713 #progress.setQuiet(True)
1716 while len(facelist):
1717 debug("\n----------------------\n")
1718 debug("len(facelits): %d\n" % len(facelist))
1721 pSign = sign(P.normal[2])
1723 # We can discard faces parallel to the view vector
1724 #if P.normal[2] == 0:
1725 # facelist.remove(P)
1731 for Q in facelist[1:]:
1733 debug("P.smooth: " + str(P.smooth) + "\n")
1734 debug("Q.smooth: " + str(Q.smooth) + "\n")
1737 qSign = sign(Q.normal[2])
1738 # TODO: check also if Q is parallel??
1740 # Test 0: We need to test only those Qs whose furthest vertex
1741 # is closer to the observer than the closest vertex of P.
1743 zP = [v.co[2] for v in P.v]
1744 zQ = [v.co[2] for v in Q.v]
1745 notZOverlap = min(zP) > max(zQ) + EPS
1749 debug("NOT Z OVERLAP!\n")
1751 # If Q is not marked then we can safely print P
1754 debug("met a marked face\n")
1758 # Test 1: X extent overlapping
1759 xP = [v.co[0] for v in P.v]
1760 xQ = [v.co[0] for v in Q.v]
1761 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
1762 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
1766 debug("NOT X OVERLAP!\n")
1770 # Test 2: Y extent Overlapping
1771 yP = [v.co[1] for v in P.v]
1772 yQ = [v.co[1] for v in Q.v]
1773 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
1774 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
1778 debug("NOT Y OVERLAP!\n")
1782 # Test 3: P vertices are all behind the plane of Q
1785 d = qSign * Distance(Vector(Pi), Q)
1788 pVerticesBehindPlaneQ = (n == len(P))
1790 if pVerticesBehindPlaneQ:
1792 debug("P BEHIND Q!\n")
1796 # Test 4: Q vertices in front of the plane of P
1799 d = pSign * Distance(Vector(Qi), P)
1802 qVerticesInFrontPlaneP = (n == len(Q))
1804 if qVerticesInFrontPlaneP:
1806 debug("Q IN FRONT OF P!\n")
1810 # Test 5: Check if projections of polygons effectively overlap,
1811 # in previous tests we checked only bounding boxes.
1813 if not projectionsOverlap(P, Q):
1815 debug("Projections do not overlap!\n")
1818 # We still can't say if P obscures Q.
1820 # But if Q is marked we do a face-split trying to resolve a
1821 # difficulty (maybe a visibility cycle).
1824 debug("Possibly a cycle detected!\n")
1825 debug("Split here!!\n")
1827 facelist = facesplit(P, Q, facelist, nmesh)
1831 # The question now is: Does Q obscure P?
1834 # Test 3bis: Q vertices are all behind the plane of P
1837 d = pSign * Distance(Vector(Qi), P)
1840 qVerticesBehindPlaneP = (n == len(Q))
1842 if qVerticesBehindPlaneP:
1843 debug("\nTest 3bis\n")
1844 debug("Q BEHIND P!\n")
1847 # Test 4bis: P vertices in front of the plane of Q
1850 d = qSign * Distance(Vector(Pi), Q)
1853 pVerticesInFrontPlaneQ = (n == len(P))
1855 if pVerticesInFrontPlaneQ:
1856 debug("\nTest 4bis\n")
1857 debug("P IN FRONT OF Q!\n")
1860 # We don't even know if Q does obscure P, so they should
1861 # intersect each other, split one of them in two parts.
1862 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
1863 debug("\nSimple Intersection?\n")
1864 debug("Test 3bis or 4bis failed\n")
1865 debug("Split here!!2\n")
1867 facelist = facesplit(P, Q, facelist, nmesh)
1872 facelist.insert(0, Q)
1875 debug("Q marked!\n")
1879 if split_done == 0 and face_marked == 0:
1885 #if facelist == None:
1887 # print [v.co for v in P]
1888 # print [v.co for v in Q]
1891 # end of while len(facelist)
1894 nmesh.faces = maplist
1895 for f in nmesh.faces:
1901 def _doHiddenSurfaceRemoval(self, mesh):
1902 """Do HSR for the given mesh.
1904 if len(mesh.faces) == 0:
1907 if config.polygons['HSR'] == 'PAINTER':
1908 print "\nUsing the Painter algorithm for HSR."
1909 self.__simpleDepthSort(mesh)
1911 elif config.polygons['HSR'] == 'NEWELL':
1912 print "\nUsing the Newell's algorithm for HSR."
1913 self.__newellDepthSort(mesh)
1916 def _doEdgesStyle(self, mesh, edgestyleSelect):
1917 """Process Mesh Edges accroding to a given selection style.
1919 Examples of algorithms:
1922 given an edge if its adjacent faces have the same normal (that is
1923 they are complanar), than deselect it.
1926 given an edge if one its adjacent faces is frontfacing and the
1927 other is backfacing, than select it, else deselect.
1930 Mesh.Mode(Mesh.SelectModes['EDGE'])
1932 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
1934 for i,edge_faces in enumerate(edge_cache):
1935 mesh.edges[i].sel = 0
1936 if edgestyleSelect(edge_faces):
1937 mesh.edges[i].sel = 1
1940 for e in mesh.edges:
1943 if edgestyleSelect(e, mesh):
1949 # ---------------------------------------------------------------------
1951 ## GUI Class and Main Program
1953 # ---------------------------------------------------------------------
1956 from Blender import BGL, Draw
1957 from Blender.BGL import *
1963 # Output Format menu
1964 output_format = config.output['FORMAT']
1965 default_value = outputWriters.keys().index(output_format)+1
1966 GUI.outFormatMenu = Draw.Create(default_value)
1967 GUI.evtOutFormatMenu = 0
1969 # Animation toggle button
1970 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1971 GUI.evtAnimToggle = 1
1973 # Join Objects toggle button
1974 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1975 GUI.evtJoinObjsToggle = 2
1977 # Render filled polygons
1978 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1980 # Shading Style menu
1981 shading_style = config.polygons['SHADING']
1982 default_value = shadingStyles.keys().index(shading_style)+1
1983 GUI.shadingStyleMenu = Draw.Create(default_value)
1984 GUI.evtShadingStyleMenu = 21
1986 GUI.evtPolygonsToggle = 3
1987 # We hide the config.polygons['EXPANSION_TRICK'], for now
1989 # Render polygon edges
1990 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1991 GUI.evtShowEdgesToggle = 4
1993 # Render hidden edges
1994 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1995 GUI.evtShowHiddenEdgesToggle = 5
1998 edge_style = config.edges['STYLE']
1999 default_value = edgeStyles.keys().index(edge_style)+1
2000 GUI.edgeStyleMenu = Draw.Create(default_value)
2001 GUI.evtEdgeStyleMenu = 6
2004 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2005 GUI.evtEdgeWidthSlider = 7
2008 c = config.edges['COLOR']
2009 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2010 GUI.evtEdgeColorPicker = 71
2013 GUI.evtRenderButton = 8
2016 GUI.evtExitButton = 9
2020 # initialize static members
2023 glClear(GL_COLOR_BUFFER_BIT)
2024 glColor3f(0.0, 0.0, 0.0)
2025 glRasterPos2i(10, 350)
2026 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2028 glRasterPos2i(10, 335)
2029 Draw.Text("Press Q or ESC to quit.")
2031 # Build the output format menu
2032 glRasterPos2i(10, 310)
2033 Draw.Text("Select the output Format:")
2034 outMenuStruct = "Output Format %t"
2035 for t in outputWriters.keys():
2036 outMenuStruct = outMenuStruct + "|%s" % t
2037 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2038 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2041 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2042 10, 260, 160, 18, GUI.animToggle.val,
2043 "Toggle rendering of animations")
2045 # Join Objects toggle
2046 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2047 10, 235, 160, 18, GUI.joinObjsToggle.val,
2048 "Join objects in the rendered file")
2051 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2053 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2056 glRasterPos2i(200, 310)
2057 Draw.Text("Rendering Style:")
2060 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
2061 200, 285, 160, 18, GUI.polygonsToggle.val,
2062 "Render filled polygons")
2064 if GUI.polygonsToggle.val == 1:
2066 # Polygon Shading Style
2067 shadingStyleMenuStruct = "Shading Style %t"
2068 for t in shadingStyles.keys():
2069 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
2070 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
2071 200, 260, 160, 18, GUI.shadingStyleMenu.val,
2072 "Choose the shading style")
2076 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
2077 200, 235, 160, 18, GUI.showEdgesToggle.val,
2078 "Render polygon edges")
2080 if GUI.showEdgesToggle.val == 1:
2083 edgeStyleMenuStruct = "Edge Style %t"
2084 for t in edgeStyles.keys():
2085 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
2086 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
2087 200, 210, 160, 18, GUI.edgeStyleMenu.val,
2088 "Choose the edge style")
2091 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
2092 200, 185, 140, 18, GUI.edgeWidthSlider.val,
2093 0.0, 10.0, 0, "Change Edge Width")
2096 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
2097 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
2100 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
2101 GUI.evtShowHiddenEdgesToggle,
2102 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
2103 "Render hidden edges as dashed lines")
2105 glRasterPos2i(10, 160)
2106 Draw.Text("%s (c) 2006" % __author__)
2108 def event(evt, val):
2110 if evt == Draw.ESCKEY or evt == Draw.QKEY:
2117 def button_event(evt):
2119 if evt == GUI.evtExitButton:
2122 elif evt == GUI.evtOutFormatMenu:
2123 i = GUI.outFormatMenu.val - 1
2124 config.output['FORMAT']= outputWriters.keys()[i]
2126 elif evt == GUI.evtAnimToggle:
2127 config.output['ANIMATION'] = bool(GUI.animToggle.val)
2129 elif evt == GUI.evtJoinObjsToggle:
2130 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
2132 elif evt == GUI.evtPolygonsToggle:
2133 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
2135 elif evt == GUI.evtShadingStyleMenu:
2136 i = GUI.shadingStyleMenu.val - 1
2137 config.polygons['SHADING'] = shadingStyles.keys()[i]
2139 elif evt == GUI.evtShowEdgesToggle:
2140 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
2142 elif evt == GUI.evtShowHiddenEdgesToggle:
2143 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
2145 elif evt == GUI.evtEdgeStyleMenu:
2146 i = GUI.edgeStyleMenu.val - 1
2147 config.edges['STYLE'] = edgeStyles.keys()[i]
2149 elif evt == GUI.evtEdgeWidthSlider:
2150 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
2152 elif evt == GUI.evtEdgeColorPicker:
2153 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
2155 elif evt == GUI.evtRenderButton:
2156 label = "Save %s" % config.output['FORMAT']
2157 # Show the File Selector
2159 Blender.Window.FileSelector(vectorize, label, outputfile)
2162 print "Event: %d not handled!" % evt
2169 from pprint import pprint
2171 pprint(config.output)
2172 pprint(config.polygons)
2173 pprint(config.edges)
2175 _init = staticmethod(_init)
2176 draw = staticmethod(draw)
2177 event = staticmethod(event)
2178 button_event = staticmethod(button_event)
2179 conf_debug = staticmethod(conf_debug)
2181 # A wrapper function for the vectorizing process
2182 def vectorize(filename):
2183 """The vectorizing process is as follows:
2185 - Instanciate the writer and the renderer
2190 print "\nERROR: invalid file name!"
2193 from Blender import Window
2194 editmode = Window.EditMode()
2195 if editmode: Window.EditMode(0)
2197 actualWriter = outputWriters[config.output['FORMAT']]
2198 writer = actualWriter(filename)
2200 renderer = Renderer()
2201 renderer.doRendering(writer, config.output['ANIMATION'])
2203 if editmode: Window.EditMode(1)
2206 if __name__ == "__main__":
2211 basename = Blender.sys.basename(Blender.Get('filename'))
2213 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
2215 if Blender.mode == 'background':
2216 progress = ConsoleProgressIndicator()
2217 vectorize(outputfile)
2219 progress = GraphicalProgressIndicator()
2220 Draw.Register(GUI.draw, GUI.event, GUI.button_event)