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)
133 # ---------------------------------------------------------------------
135 ## Mesh Utility class
137 # ---------------------------------------------------------------------
140 def buildEdgeFaceUsersCache(me):
142 Takes a mesh and returns a list aligned with the meshes edges.
143 Each item is a list of the faces that use the edge
144 would be the equiv for having ed.face_users as a property
146 Taken from .blender/scripts/bpymodules/BPyMesh.py,
147 thanks to ideasman_42.
150 def sorted_edge_indicies(ed):
158 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
160 fvi= [v.index for v in f.v]# face vert idx's
161 for i in xrange(len(f)):
168 face_edges_dict[i1,i2][1].append(f)
170 face_edges= [None] * len(me.edges)
171 for ed_index, ed_faces in face_edges_dict.itervalues():
172 face_edges[ed_index]= ed_faces
176 def isMeshEdge(adjacent_faces):
179 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
180 Note: if the edge has no adjacent faces we want to show it as well,
181 useful for "edge only" portion of objects.
184 if len(adjacent_faces) == 0:
187 selected_faces = [f for f in adjacent_faces if f.sel]
189 if len(selected_faces) != 0:
194 def isSilhouetteEdge(adjacent_faces):
195 """Silhuette selection rule.
197 An edge is a silhuette edge if it is shared by two faces with
198 different selection status or if it is a boundary edge of a selected
202 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
203 (len(adjacent_faces) == 2 and
204 adjacent_faces[0].sel != adjacent_faces[1].sel)
210 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
211 isMeshEdge = staticmethod(isMeshEdge)
212 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
215 # ---------------------------------------------------------------------
217 ## Shading Utility class
219 # ---------------------------------------------------------------------
224 def toonShadingMapSetup():
225 levels = config.polygons['TOON_LEVELS']
227 texels = 2*levels - 1
228 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
234 shademap = ShadingUtils.shademap
237 shademap = ShadingUtils.toonShadingMapSetup()
240 for i in xrange(0, len(shademap)-1):
241 pivot = (shademap[i]+shademap[i+1])/2.0
246 if v < shademap[i+1]:
251 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
252 toonShading = staticmethod(toonShading)
255 # ---------------------------------------------------------------------
257 ## Projections classes
259 # ---------------------------------------------------------------------
262 """Calculate the projection of an object given the camera.
264 A projector is useful to so some per-object transformation to obtain the
265 projection of an object given the camera.
267 The main method is #doProjection# see the method description for the
271 def __init__(self, cameraObj, canvasRatio):
272 """Calculate the projection matrix.
274 The projection matrix depends, in this case, on the camera settings.
275 TAKE CARE: This projector expects vertices in World Coordinates!
278 camera = cameraObj.getData()
280 aspect = float(canvasRatio[0])/float(canvasRatio[1])
281 near = camera.clipStart
284 scale = float(camera.scale)
286 fovy = atan(0.5/aspect/(camera.lens/32))
287 fovy = fovy * 360.0/pi
289 # What projection do we want?
291 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
292 elif camera.type == 1:
293 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
295 # View transformation
296 cam = Matrix(cameraObj.getInverseMatrix())
301 self.projectionMatrix = mP
307 def doProjection(self, v):
308 """Project the point on the view plane.
310 Given a vertex calculate the projection using the current projection
314 # Note that we have to work on the vertex using homogeneous coordinates
315 # From blender 2.42+ we don't need to resize the vector to be 4d
316 # when applying a 4x4 matrix, but we do that anyway since we need the
317 # 4th coordinate later
318 p = self.projectionMatrix * Vector(v).resize4D()
320 # Perspective division
337 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
338 """Return a perspective projection matrix.
341 top = near * tan(fovy * pi / 360.0)
345 x = (2.0 * near) / (right-left)
346 y = (2.0 * near) / (top-bottom)
347 a = (right+left) / (right-left)
348 b = (top+bottom) / (top - bottom)
349 c = - ((far+near) / (far-near))
350 d = - ((2*far*near)/(far-near))
356 [0.0, 0.0, -1.0, 0.0])
360 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
361 """Return an orthogonal projection matrix.
364 # The 11 in the formula was found emiprically
365 top = near * tan(fovy * pi / 360.0) * (scale * 11)
367 left = bottom * aspect
372 tx = -((right+left)/rl)
373 ty = -((top+bottom)/tb)
377 [2.0/rl, 0.0, 0.0, tx],
378 [0.0, 2.0/tb, 0.0, ty],
379 [0.0, 0.0, 2.0/fn, tz],
380 [0.0, 0.0, 0.0, 1.0])
385 # ---------------------------------------------------------------------
387 ## Progress Indicator
389 # ---------------------------------------------------------------------
392 """A model for a progress indicator.
394 Do the progress calculation calculation and
395 the view independent stuff of a progress indicator.
397 def __init__(self, steps=0):
403 def setSteps(self, steps):
404 """Set the number of steps of the activity wich we want to track.
411 def setName(self, name):
412 """Set the name of the activity wich we want to track.
419 def getProgress(self):
427 """Update the model, call this method when one step is completed.
429 if self.progress == 100:
433 self.progress = ( float(self.completed) / float(self.steps) ) * 100
434 self.progress = int(self.progress)
439 class ProgressIndicator:
440 """An abstraction of a View for the Progress Model
444 # Use a refresh rate so we do not show the progress at
445 # every update, but every 'self.refresh_rate' times.
446 self.refresh_rate = 10
447 self.shows_counter = 0
451 self.progressModel = None
453 def setQuiet(self, value):
456 def setActivity(self, name, steps):
457 """Initialize the Model.
459 In a future version (with subactivities-progress support) this method
460 could only set the current activity.
462 self.progressModel = Progress()
463 self.progressModel.setName(name)
464 self.progressModel.setSteps(steps)
466 def getActivity(self):
467 return self.progressModel
470 """Update the model and show the actual progress.
472 assert(self.progressModel)
474 if self.progressModel.update():
478 self.show(self.progressModel.getProgress(),
479 self.progressModel.getName())
481 # We return always True here so we can call the update() method also
482 # from lambda funcs (putting the call in logical AND with other ops)
485 def show(self, progress, name=""):
486 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
487 if self.shows_counter != 0:
491 self.shows_counter = -1
494 class ConsoleProgressIndicator(ProgressIndicator):
495 """Show a progress bar on stderr, a la wget.
498 ProgressIndicator.__init__(self)
500 self.swirl_chars = ["-", "\\", "|", "/"]
501 self.swirl_count = -1
503 def show(self, progress, name):
504 ProgressIndicator.show(self, progress, name)
507 bar_progress = int( (progress/100.0) * bar_length )
508 bar = ("=" * bar_progress).ljust(bar_length)
510 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
511 swirl_char = self.swirl_chars[self.swirl_count]
513 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
515 sys.stderr.write(progress_bar+"\r")
517 sys.stderr.write("\n")
520 class GraphicalProgressIndicator(ProgressIndicator):
521 """Interface to the Blender.Window.DrawProgressBar() method.
524 ProgressIndicator.__init__(self)
526 #self.swirl_chars = ["-", "\\", "|", "/"]
527 # We have to use letters with the same width, for now!
528 # Blender progress bar considers the font widths when
529 # calculating the progress bar width.
530 self.swirl_chars = ["\\", "/"]
531 self.swirl_count = -1
533 def show(self, progress, name):
534 ProgressIndicator.show(self, progress)
536 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
537 swirl_char = self.swirl_chars[self.swirl_count]
539 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
541 # Finally draw the Progress Bar
542 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
543 Window.DrawProgressBar(progress/100.0, progress_text)
546 Window.DrawProgressBar(1, progress_text)
551 # ---------------------------------------------------------------------
553 ## 2D Object representation class
555 # ---------------------------------------------------------------------
557 # TODO: a class to represent the needed properties of a 2D vector image
558 # For now just using a [N]Mesh structure.
561 # ---------------------------------------------------------------------
563 ## Vector Drawing Classes
565 # ---------------------------------------------------------------------
571 A class for printing output in a vectorial format.
573 Given a 2D representation of the 3D scene the class is responsible to
574 write it is a vector format.
576 Every subclasses of VectorWriter must have at last the following public
580 - printCanvas(self, scene,
581 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
584 def __init__(self, fileName):
585 """Set the output file name and other properties"""
587 self.outputFileName = fileName
590 context = Scene.GetCurrent().getRenderingContext()
591 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
595 self.animation = False
602 def open(self, startFrame=1, endFrame=1):
603 if startFrame != endFrame:
604 self.startFrame = startFrame
605 self.endFrame = endFrame
606 self.animation = True
608 self.file = open(self.outputFileName, "w")
609 print "Outputting to: ", self.outputFileName
617 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
618 showHiddenEdges=False):
619 """This is the interface for the needed printing routine.
626 class SVGVectorWriter(VectorWriter):
627 """A concrete class for writing SVG output.
630 def __init__(self, fileName):
631 """Simply call the parent Contructor.
633 VectorWriter.__init__(self, fileName)
640 def open(self, startFrame=1, endFrame=1):
641 """Do some initialization operations.
643 VectorWriter.open(self, startFrame, endFrame)
647 """Do some finalization operation.
651 # remember to call the close method of the parent
652 VectorWriter.close(self)
655 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
656 showHiddenEdges=False):
657 """Convert the scene representation to SVG.
660 Objects = scene.getChildren()
662 context = scene.getRenderingContext()
663 framenumber = context.currentFrame()
666 framestyle = "display:none"
668 framestyle = "display:block"
670 # Assign an id to this group so we can set properties on it using DOM
671 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
672 (framenumber, framestyle) )
677 if(obj.getType() != 'Mesh'):
680 self.file.write("<g id=\"%s\">\n" % obj.getName())
682 mesh = obj.getData(mesh=1)
685 self._printPolygons(mesh)
688 self._printEdges(mesh, showHiddenEdges)
690 self.file.write("</g>\n")
692 self.file.write("</g>\n")
699 def _calcCanvasCoord(self, v):
700 """Convert vertex in scene coordinates to canvas coordinates.
703 pt = Vector([0, 0, 0])
705 mW = float(self.canvasSize[0])/2.0
706 mH = float(self.canvasSize[1])/2.0
708 # rescale to canvas size
709 pt[0] = v.co[0]*mW + mW
710 pt[1] = v.co[1]*mH + mH
713 # For now we want (0,0) in the top-left corner of the canvas.
714 # Mirror and translate along y
716 pt[1] += self.canvasSize[1]
720 def _printHeader(self):
721 """Print SVG header."""
723 self.file.write("<?xml version=\"1.0\"?>\n")
724 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
725 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
726 self.file.write("<svg version=\"1.0\"\n")
727 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
728 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
733 self.file.write("""\n<script type="text/javascript"><![CDATA[
737 /* FIXME: Use 1000 as interval as lower values gives problems */
738 timerID = setInterval("NextFrame()", 1000);
739 globalFrameCounter=%d;
743 currentElement = document.getElementById('frame'+globalFrameCounter)
744 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
751 if (globalFrameCounter > globalEndFrame)
753 clearInterval(timerID)
759 previousElement.style.display="none";
761 currentElement.style.display="block";
762 globalFrameCounter++;
766 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
768 def _printFooter(self):
769 """Print the SVG footer."""
771 self.file.write("\n</svg>\n")
773 def _printPolygons(self, mesh):
774 """Print the selected (visible) polygons.
777 if len(mesh.faces) == 0:
780 self.file.write("<g>\n")
782 for face in mesh.faces:
786 self.file.write("<path d=\"")
788 p = self._calcCanvasCoord(face.verts[0])
789 self.file.write("M %g,%g L " % (p[0], p[1]))
791 for v in face.verts[1:]:
792 p = self._calcCanvasCoord(v)
793 self.file.write("%g,%g " % (p[0], p[1]))
795 # get rid of the last blank space, just cosmetics here.
796 self.file.seek(-1, 1)
797 self.file.write(" z\"\n")
799 # take as face color the first vertex color
802 color = [fcol.r, fcol.g, fcol.b, fcol.a]
804 color = [255, 255, 255, 255]
806 # Convert the color to the #RRGGBB form
807 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
809 # Handle transparent polygons
812 opacity = float(color[3])/255.0
813 #opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
814 opacity_string = "opacity: %g;" % (opacity)
816 self.file.write("\tstyle=\"fill:" + str_col + ";")
817 self.file.write(opacity_string)
819 # use the stroke property to alleviate the "adjacent edges" problem,
820 # we simulate polygon expansion using borders,
821 # see http://www.antigrain.com/svg/index.html for more info
824 # EXPANSION TRICK is not that useful where there is transparency
825 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
826 # str_col = "#000000" # For debug
827 self.file.write(" stroke:%s;\n" % str_col)
828 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
829 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
831 self.file.write("\"/>\n")
833 self.file.write("</g>\n")
835 def _printEdges(self, mesh, showHiddenEdges=False):
836 """Print the wireframe using mesh edges.
839 stroke_width = config.edges['WIDTH']
840 stroke_col = config.edges['COLOR']
842 self.file.write("<g>\n")
846 hidden_stroke_style = ""
849 if showHiddenEdges == False:
852 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
854 p1 = self._calcCanvasCoord(e.v1)
855 p2 = self._calcCanvasCoord(e.v2)
857 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
858 % ( p1[0], p1[1], p2[0], p2[1] ) )
859 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
860 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
861 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
862 self.file.write(hidden_stroke_style)
863 self.file.write("\"/>\n")
865 self.file.write("</g>\n")
868 # ---------------------------------------------------------------------
872 # ---------------------------------------------------------------------
874 # A dictionary to collect different shading style methods
875 shadingStyles = dict()
876 shadingStyles['FLAT'] = None
877 shadingStyles['TOON'] = None
879 # A dictionary to collect different edge style methods
881 edgeStyles['MESH'] = MeshUtils.isMeshEdge
882 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
884 # A dictionary to collect the supported output formats
885 outputWriters = dict()
886 outputWriters['SVG'] = SVGVectorWriter
890 """Render a scene viewed from the active camera.
892 This class is responsible of the rendering process, transformation and
893 projection of the objects in the scene are invoked by the renderer.
895 The rendering is done using the active camera for the current scene.
899 """Make the rendering process only for the current scene by default.
901 We will work on a copy of the scene, to be sure that the current scene do
902 not get modified in any way.
905 # Render the current Scene, this should be a READ-ONLY property
906 self._SCENE = Scene.GetCurrent()
908 # Use the aspect ratio of the scene rendering context
909 context = self._SCENE.getRenderingContext()
911 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
912 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
913 float(context.aspectRatioY())
916 # Render from the currently active camera
917 self.cameraObj = self._SCENE.getCurrentCamera()
919 # Get a projector for this camera.
920 # NOTE: the projector wants object in world coordinates,
921 # so we should remember to apply modelview transformations
922 # _before_ we do projection transformations.
923 self.proj = Projector(self.cameraObj, self.canvasRatio)
925 # Get the list of lighting sources
926 obj_lst = self._SCENE.getChildren()
927 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
929 # When there are no lights we use a default lighting source
930 # that have the same position of the camera
931 if len(self.lights) == 0:
933 lobj = Object.New('Lamp')
934 lobj.loc = self.cameraObj.loc
936 self.lights.append(lobj)
943 def doRendering(self, outputWriter, animation=False):
944 """Render picture or animation and write it out.
947 - a Vector writer object that will be used to output the result.
948 - a flag to tell if we want to render an animation or only the
952 context = self._SCENE.getRenderingContext()
953 origCurrentFrame = context.currentFrame()
955 # Handle the animation case
957 startFrame = origCurrentFrame
958 endFrame = startFrame
961 startFrame = context.startFrame()
962 endFrame = context.endFrame()
963 outputWriter.open(startFrame, endFrame)
965 # Do the rendering process frame by frame
966 print "Start Rendering of %d frames" % (endFrame-startFrame)
967 for f in xrange(startFrame, endFrame+1):
968 print "\n\nFrame: %d" % f
969 context.currentFrame(f)
971 # Use some temporary workspace, a full copy of the scene
972 inputScene = self._SCENE.copy(2)
973 # And Set our camera accordingly
974 self.cameraObj = inputScene.getCurrentCamera()
977 renderedScene = self.doRenderScene(inputScene)
979 print "There was an error! Aborting."
981 print traceback.print_exc()
983 self._SCENE.makeCurrent()
984 Scene.unlink(inputScene)
988 outputWriter.printCanvas(renderedScene,
989 doPrintPolygons = config.polygons['SHOW'],
990 doPrintEdges = config.edges['SHOW'],
991 showHiddenEdges = config.edges['SHOW_HIDDEN'])
993 # delete the rendered scene
994 self._SCENE.makeCurrent()
995 Scene.unlink(renderedScene)
1000 context.currentFrame(origCurrentFrame)
1003 def doRenderScene(self, workScene):
1004 """Control the rendering process.
1006 Here we control the entire rendering process invoking the operation
1007 needed to transform and project the 3D scene in two dimensions.
1010 # global processing of the scene
1012 self._doSceneClipping(workScene)
1014 self._doConvertGeometricObjsToMesh(workScene)
1016 if config.output['JOIN_OBJECTS']:
1017 self._joinMeshObjectsInScene(workScene)
1019 self._doSceneDepthSorting(workScene)
1021 # Per object activities
1023 Objects = workScene.getChildren()
1024 print "Total Objects: %d" % len(Objects)
1025 for i,obj in enumerate(Objects):
1027 print "Rendering Object: %d" % i
1029 if obj.getType() != 'Mesh':
1030 print "Only Mesh supported! - Skipping type:", obj.getType()
1033 print "Rendering: ", obj.getName()
1035 mesh = obj.getData(mesh=1)
1037 self._doModelingTransformation(mesh, obj.matrix)
1039 self._doBackFaceCulling(mesh)
1041 # When doing HSR with NEWELL we may want to flip all normals
1043 if config.polygons['HSR'] == "NEWELL":
1044 for f in mesh.faces:
1047 for f in mesh.faces:
1050 self._doLighting(mesh)
1053 # Do "projection" now so we perform further processing
1054 # in Normalized View Coordinates
1055 self._doProjection(mesh, self.proj)
1057 self._doViewFrustumClipping(mesh)
1059 self._doHiddenSurfaceRemoval(mesh)
1061 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
1064 # Update the object data, important! :)
1076 def _getObjPosition(self, obj):
1077 """Return the obj position in World coordinates.
1079 return obj.matrix.translationPart()
1081 def _cameraViewVector(self):
1082 """Get the View Direction form the camera matrix.
1084 return Vector(self.cameraObj.matrix[2]).resize3D()
1089 def _isFaceVisible(self, face):
1090 """Determine if a face of an object is visible from the current camera.
1092 The view vector is calculated from the camera location and one of the
1093 vertices of the face (expressed in World coordinates, after applying
1094 modelview transformations).
1096 After those transformations we determine if a face is visible by
1097 computing the angle between the face normal and the view vector, this
1098 angle has to be between -90 and 90 degrees for the face to be visible.
1099 This corresponds somehow to the dot product between the two, if it
1100 results > 0 then the face is visible.
1102 There is no need to normalize those vectors since we are only interested in
1103 the sign of the cross product and not in the product value.
1105 NOTE: here we assume the face vertices are in WorldCoordinates, so
1106 please transform the object _before_ doing the test.
1109 normal = Vector(face.no)
1110 camPos = self._getObjPosition(self.cameraObj)
1113 # View Vector in orthographics projections is the view Direction of
1115 if self.cameraObj.data.getType() == 1:
1116 view_vect = self._cameraViewVector()
1118 # View vector in perspective projections can be considered as
1119 # the difference between the camera position and one point of
1120 # the face, we choose the farthest point from the camera.
1121 if self.cameraObj.data.getType() == 0:
1122 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
1126 # if d > 0 the face is visible from the camera
1127 d = view_vect * normal
1137 def _doSceneClipping(self, scene):
1138 """Clip whole objects against the View Frustum.
1140 For now clip away only objects according to their center position.
1143 cpos = self._getObjPosition(self.cameraObj)
1144 view_vect = self._cameraViewVector()
1146 near = self.cameraObj.data.clipStart
1147 far = self.cameraObj.data.clipEnd
1149 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
1150 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
1151 fovy = fovy * 360.0/pi
1153 Objects = scene.getChildren()
1155 if o.getType() != 'Mesh': continue;
1157 obj_vect = Vector(cpos) - self._getObjPosition(o)
1159 d = obj_vect*view_vect
1160 theta = AngleBetweenVecs(obj_vect, view_vect)
1162 # if the object is outside the view frustum, clip it away
1163 if (d < near) or (d > far) or (theta > fovy):
1166 def _doConvertGeometricObjsToMesh(self, scene):
1167 """Convert all "geometric" objects to mesh ones.
1169 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
1170 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
1172 Objects = scene.getChildren()
1173 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
1176 obj = self._convertToRawMeshObj(obj)
1178 scene.unlink(old_obj)
1181 # XXX Workaround for Text and Curve which have some normals
1182 # inverted when they are converted to Mesh, REMOVE that when
1183 # blender will fix that!!
1184 if old_obj.getType() in ['Curve', 'Text']:
1185 me = obj.getData(mesh=1)
1186 for f in me.faces: f.sel = 1;
1187 for v in me.verts: v.sel = 1;
1194 def _doSceneDepthSorting(self, scene):
1195 """Sort objects in the scene.
1197 The object sorting is done accordingly to the object centers.
1200 c = self._getObjPosition(self.cameraObj)
1202 by_center_pos = (lambda o1, o2:
1203 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
1204 cmp((self._getObjPosition(o1) - Vector(c)).length,
1205 (self._getObjPosition(o2) - Vector(c)).length)
1208 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
1209 # then ob1 goes farther than obj2, useful when obj2 has holes
1212 Objects = scene.getChildren()
1213 Objects.sort(by_center_pos)
1220 def _joinMeshObjectsInScene(self, scene):
1221 """Merge all the Mesh Objects in a scene into a single Mesh Object.
1224 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
1226 # FIXME: Object.join() do not work if the list contains 1 object
1230 mesh = Mesh.New('BigOne')
1231 bigObj = Object.New('Mesh', 'BigOne')
1238 except RuntimeError:
1239 print "\nWarning! - Can't Join Objects\n"
1240 scene.unlink(bigObj)
1243 print "Objects Type error?"
1251 # Per object/mesh methods
1253 def _convertToRawMeshObj(self, object):
1254 """Convert geometry based object to a mesh object.
1256 me = Mesh.New('RawMesh_'+object.name)
1257 me.getFromObject(object.name)
1259 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
1262 # If the object has no materials set a default material
1263 if not me.materials:
1264 me.materials = [Material.New()]
1265 #for f in me.faces: f.mat = 0
1267 newObject.setMatrix(object.getMatrix())
1271 def _doModelingTransformation(self, mesh, matrix):
1272 """Transform object coordinates to world coordinates.
1274 This step is done simply applying to the object its tranformation
1275 matrix and recalculating its normals.
1277 # XXX FIXME: blender do not transform normals in the right way when
1278 # there are negative scale values
1279 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
1280 print "WARNING: Negative scales, expect incorrect results!"
1282 mesh.transform(matrix, True)
1284 def _doBackFaceCulling(self, mesh):
1285 """Simple Backface Culling routine.
1287 At this level we simply do a visibility test face by face and then
1288 select the vertices belonging to visible faces.
1291 # Select all vertices, so edges can be displayed even if there are no
1293 for v in mesh.verts:
1296 Mesh.Mode(Mesh.SelectModes['FACE'])
1298 for f in mesh.faces:
1300 if self._isFaceVisible(f):
1303 def _doLighting(self, mesh):
1304 """Apply an Illumination and shading model to the object.
1306 The model used is the Phong one, it may be inefficient,
1307 but I'm just learning about rendering and starting from Phong seemed
1308 the most natural way.
1311 # If the mesh has vertex colors already, use them,
1312 # otherwise turn them on and do some calculations
1313 if mesh.vertexColors:
1315 mesh.vertexColors = 1
1317 materials = mesh.materials
1319 camPos = self._getObjPosition(self.cameraObj)
1321 # We do per-face color calculation (FLAT Shading), we can easily turn
1322 # to a per-vertex calculation if we want to implement some shading
1323 # technique. For an example see:
1324 # http://www.miralab.unige.ch/papers/368.pdf
1325 for f in mesh.faces:
1331 mat = materials[f.mat]
1333 # A new default material
1335 mat = Material.New('defMat')
1337 # Check if it is a shadeless material
1338 elif mat.getMode() & Material.Modes['SHADELESS']:
1340 # Convert to a value between 0 and 255
1341 tmp_col = [ int(c * 255.0) for c in I]
1352 # do vertex color calculation
1354 TotDiffSpec = Vector([0.0, 0.0, 0.0])
1356 for l in self.lights:
1358 light_pos = self._getObjPosition(l)
1359 light = light_obj.data
1361 L = Vector(light_pos).normalize()
1363 V = (Vector(camPos) - Vector(f.cent)).normalize()
1365 N = Vector(f.no).normalize()
1367 if config.polygons['SHADING'] == 'TOON':
1368 NL = ShadingUtils.toonShading(N*L)
1372 # Should we use NL instead of (N*L) here?
1373 R = 2 * (N*L) * N - L
1375 Ip = light.getEnergy()
1377 # Diffuse co-efficient
1378 kd = mat.getRef() * Vector(mat.getRGBCol())
1380 kd[i] *= light.col[i]
1382 Idiff = Ip * kd * max(0, NL)
1385 # Specular component
1386 ks = mat.getSpec() * Vector(mat.getSpecCol())
1387 ns = mat.getHardness()
1388 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
1390 TotDiffSpec += (Idiff+Ispec)
1394 Iamb = Vector(Blender.World.Get()[0].getAmb())
1397 # Emissive component (convert to a triplet)
1398 ki = Vector([mat.getEmit()]*3)
1400 #I = ki + Iamb + (Idiff + Ispec)
1401 I = ki + (ka * Iamb) + TotDiffSpec
1404 # Set Alpha component
1406 I.append(mat.getAlpha())
1408 # Clamp I values between 0 and 1
1409 I = [ min(c, 1) for c in I]
1410 I = [ max(0, c) for c in I]
1412 # Convert to a value between 0 and 255
1413 tmp_col = [ int(c * 255.0) for c in I]
1421 def _doProjection(self, mesh, projector):
1422 """Apply Viewing and Projection tranformations.
1425 for v in mesh.verts:
1426 p = projector.doProjection(v.co[:])
1431 #mesh.recalcNormals()
1434 # We could reeset Camera matrix, since now
1435 # we are in Normalized Viewing Coordinates,
1436 # but doung that would affect World Coordinate
1437 # processing for other objects
1439 #self.cameraObj.data.type = 1
1440 #self.cameraObj.data.scale = 2.0
1441 #m = Matrix().identity()
1442 #self.cameraObj.setMatrix(m)
1444 def _doViewFrustumClipping(self, mesh):
1445 """Clip faces against the View Frustum.
1449 def __simpleDepthSort(self, mesh):
1450 """Sort faces by the furthest vertex.
1452 This simple mesthod is known also as the painter algorithm, and it
1453 solves HSR correctly only for convex meshes.
1457 # The sorting requires circa n*log(n) steps
1459 progress.setActivity("HSR: Painter", n*log(n))
1462 by_furthest_z = (lambda f1, f2: progress.update() and
1463 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
1466 # FIXME: using NMesh to sort faces. We should avoid that!
1467 nmesh = NMesh.GetRaw(mesh.name)
1469 # remember that _higher_ z values mean further points
1470 nmesh.faces.sort(by_furthest_z)
1471 nmesh.faces.reverse()
1475 def __topologicalDepthSort(self, mesh):
1476 """Occlusion based on topological occlusion.
1478 Build the occlusion graph of the mesh,
1479 and then do topological sort on that graph
1483 def __newellDepthSort(self, mesh):
1484 """Newell's depth sorting.
1489 by_furthest_z = (lambda f1, f2:
1490 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
1493 mesh.quadToTriangle()
1495 from split import Distance, isOnSegment
1497 def projectionsOverlap(P, Q):
1499 for i in range(0, len(P.v)):
1501 v1 = Vector(P.v[i-1])
1508 for j in range(0, len(Q.v)):
1510 v3 = Vector(Q.v[j-1])
1515 #print "\n\nTEST if we have coincidence!"
1523 #print d1, d2, d3, d4
1524 #print "-----------------------\n"
1526 if d1 < EPS or d2 < EPS or d3 < EPS or d4 < EPS:
1529 # TODO: Replace with LineIntersect2D in newer API
1530 ret = LineIntersect(v1, v2, v3, v4)
1532 # if line v1-v2 and v3-v4 intersect both return
1533 # values are the same.
1534 if ret and ret[0] == ret[1] and isOnSegment(v1, v2, ret[0], True) and isOnSegment(v3, v4, ret[1], True):
1536 #l1 = (ret[0] - v1).length
1537 #l2 = (ret[0] - v2).length
1539 #l3 = (ret[1] - v3).length
1540 #l4 = (ret[1] - v4).length
1542 #print "New DISTACES againt the intersection point:"
1543 #print l1, l2, l3, l4
1544 #print "-----------------------\n"
1546 #if l1 < EPS or l2 < EPS or l3 < EPS or l4 < EPS:
1549 debug("Projections OVERLAP!!\n")
1551 " M "+ str(v1[0])+','+str(v1[1]) + ' L ' + str(v2[0])+','+str(v2[1]) + '\n' +
1552 " M "+ str(v3[0])+','+str(v3[1]) + ' L ' + str(v4[0])+','+str(v4[1]) + '\n' +
1554 debug("return: "+ str(ret)+"\n")
1560 from facesplit import facesplit
1562 # FIXME: using NMesh to sort faces. We should avoid that!
1563 nmesh = NMesh.GetRaw(mesh.name)
1565 # remember that _higher_ z values mean further points
1566 nmesh.faces.sort(by_furthest_z)
1567 nmesh.faces.reverse()
1570 # Begin depth sort tests
1572 # use the smooth flag to set marked faces
1573 for f in nmesh.faces:
1576 facelist = nmesh.faces[:]
1583 # The steps are _at_least_ equal to len(facelist), we do not count the
1584 # feces coming out from splitting!!
1585 progress.setActivity("HSR: Newell", len(facelist))
1586 #progress.setQuiet(True)
1592 while len(facelist):
1593 debug("\n----------------------\n")
1594 debug("len(facelits): %d\n" % len(facelist))
1597 pSign = sign(P.normal[2])
1599 # We can discard faces parallel to the view vector
1607 for Q in facelist[1:]:
1609 debug("P.smooth: " + str(P.smooth) + "\n")
1610 debug("Q.smooth: " + str(Q.smooth) + "\n")
1613 qSign = sign(Q.normal[2])
1615 # We need to test only those Qs whose furthest vertex
1616 # is closer to the observer than the closest vertex of P.
1618 zP = [v.co[2] for v in P.v]
1619 zQ = [v.co[2] for v in Q.v]
1620 notZOverlap = min(zP) > max(zQ) + EPS
1624 debug("NOT Z OVERLAP!\n")
1626 # If Q is not marked then we can safely print P
1629 debug("met a marked face\n")
1632 # Test 1: X extent overlapping
1633 xP = [v.co[0] for v in P.v]
1634 xQ = [v.co[0] for v in Q.v]
1635 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
1636 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
1640 debug("NOT X OVERLAP!\n")
1643 # Test 2: Y extent Overlapping
1644 yP = [v.co[1] for v in P.v]
1645 yQ = [v.co[1] for v in Q.v]
1646 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
1647 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
1651 debug("NOT Y OVERLAP!\n")
1655 # Test 3: P vertices are all behind the plane of Q
1658 d = qSign * Distance(Vector(Pi), Q)
1661 pVerticesBehindPlaneQ = (n == len(P))
1663 if pVerticesBehindPlaneQ:
1665 debug("P BEHIND Q!\n")
1669 # Test 4: Q vertices in front of the plane of P
1672 d = pSign * Distance(Vector(Qi), P)
1675 qVerticesInFrontPlaneP = (n == len(Q))
1677 if qVerticesInFrontPlaneP:
1679 debug("Q IN FRONT OF P!\n")
1682 # Test 5: Line Intersections... TODO
1683 # Check if polygons effectively overlap each other, not only
1684 # boundig boxes as done before.
1685 # Since we We are working in normalized projection coordinates
1686 # we kust check if polygons intersect.
1688 if not projectionsOverlap(P, Q):
1690 debug("Projections do not overlap!\n")
1694 # We still do not know if P obscures Q.
1696 # But if Q is marked we do a split trying to resolve a
1697 # difficulty (maybe a visibility cycle).
1700 debug("Possibly a cycle detected!\n")
1701 debug("Split here!!\n")
1703 facelist = facesplit(P, Q, facelist, nmesh)
1707 # The question now is: Does Q obscure P?
1709 # Test 3bis: Q vertices are all behind the plane of P
1712 d = pSign * Distance(Vector(Qi), P)
1715 qVerticesBehindPlaneP = (n == len(Q))
1717 if qVerticesBehindPlaneP:
1718 debug("\nTest 3bis\n")
1719 debug("Q BEHIND P!\n")
1722 # Test 4bis: P vertices in front of the plane of Q
1725 d = qSign * Distance(Vector(Pi), Q)
1728 pVerticesInFrontPlaneQ = (n == len(P))
1730 if pVerticesInFrontPlaneQ:
1731 debug("\nTest 4bis\n")
1732 debug("P IN FRONT OF Q!\n")
1735 # We don't even know if Q does obscure P, so they should
1736 # intersect each other, split one of them in two parts.
1737 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
1738 debug("\nSimple Intersection?\n")
1739 debug("Test 3bis or 4bis failed\n")
1740 debug("Split here!!2\n")
1742 facelist = facesplit(P, Q, facelist, nmesh)
1747 facelist.insert(0, Q)
1750 debug("Q marked!\n")
1754 if split_done == 0 and face_marked == 0:
1760 # end of while len(facelist)
1763 nmesh.faces = maplist
1765 for f in nmesh.faces:
1770 def _doHiddenSurfaceRemoval(self, mesh):
1771 """Do HSR for the given mesh.
1773 if len(mesh.faces) == 0:
1776 if config.polygons['HSR'] == 'PAINTER':
1777 print "\nUsing the Painter algorithm for HSR."
1778 self.__simpleDepthSort(mesh)
1780 elif config.polygons['HSR'] == 'NEWELL':
1781 print "\nUsing the Newell's algorithm for HSR."
1782 self.__newellDepthSort(mesh)
1785 def _doEdgesStyle(self, mesh, edgestyleSelect):
1786 """Process Mesh Edges accroding to a given selection style.
1788 Examples of algorithms:
1791 given an edge if its adjacent faces have the same normal (that is
1792 they are complanar), than deselect it.
1795 given an edge if one its adjacent faces is frontfacing and the
1796 other is backfacing, than select it, else deselect.
1799 Mesh.Mode(Mesh.SelectModes['EDGE'])
1801 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
1803 for i,edge_faces in enumerate(edge_cache):
1804 mesh.edges[i].sel = 0
1805 if edgestyleSelect(edge_faces):
1806 mesh.edges[i].sel = 1
1809 for e in mesh.edges:
1812 if edgestyleSelect(e, mesh):
1818 # ---------------------------------------------------------------------
1820 ## GUI Class and Main Program
1822 # ---------------------------------------------------------------------
1825 from Blender import BGL, Draw
1826 from Blender.BGL import *
1832 # Output Format menu
1833 output_format = config.output['FORMAT']
1834 default_value = outputWriters.keys().index(output_format)+1
1835 GUI.outFormatMenu = Draw.Create(default_value)
1836 GUI.evtOutFormatMenu = 0
1838 # Animation toggle button
1839 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
1840 GUI.evtAnimToggle = 1
1842 # Join Objects toggle button
1843 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
1844 GUI.evtJoinObjsToggle = 2
1846 # Render filled polygons
1847 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
1849 # Shading Style menu
1850 shading_style = config.polygons['SHADING']
1851 default_value = shadingStyles.keys().index(shading_style)+1
1852 GUI.shadingStyleMenu = Draw.Create(default_value)
1853 GUI.evtShadingStyleMenu = 21
1855 GUI.evtPolygonsToggle = 3
1856 # We hide the config.polygons['EXPANSION_TRICK'], for now
1858 # Render polygon edges
1859 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
1860 GUI.evtShowEdgesToggle = 4
1862 # Render hidden edges
1863 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
1864 GUI.evtShowHiddenEdgesToggle = 5
1867 edge_style = config.edges['STYLE']
1868 default_value = edgeStyles.keys().index(edge_style)+1
1869 GUI.edgeStyleMenu = Draw.Create(default_value)
1870 GUI.evtEdgeStyleMenu = 6
1873 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
1874 GUI.evtEdgeWidthSlider = 7
1877 c = config.edges['COLOR']
1878 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
1879 GUI.evtEdgeColorPicker = 71
1882 GUI.evtRenderButton = 8
1885 GUI.evtExitButton = 9
1889 # initialize static members
1892 glClear(GL_COLOR_BUFFER_BIT)
1893 glColor3f(0.0, 0.0, 0.0)
1894 glRasterPos2i(10, 350)
1895 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
1897 glRasterPos2i(10, 335)
1898 Draw.Text("Press Q or ESC to quit.")
1900 # Build the output format menu
1901 glRasterPos2i(10, 310)
1902 Draw.Text("Select the output Format:")
1903 outMenuStruct = "Output Format %t"
1904 for t in outputWriters.keys():
1905 outMenuStruct = outMenuStruct + "|%s" % t
1906 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1907 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1910 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1911 10, 260, 160, 18, GUI.animToggle.val,
1912 "Toggle rendering of animations")
1914 # Join Objects toggle
1915 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1916 10, 235, 160, 18, GUI.joinObjsToggle.val,
1917 "Join objects in the rendered file")
1920 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1922 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1925 glRasterPos2i(200, 310)
1926 Draw.Text("Rendering Style:")
1929 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1930 200, 285, 160, 18, GUI.polygonsToggle.val,
1931 "Render filled polygons")
1933 if GUI.polygonsToggle.val == 1:
1935 # Polygon Shading Style
1936 shadingStyleMenuStruct = "Shading Style %t"
1937 for t in shadingStyles.keys():
1938 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
1939 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
1940 200, 260, 160, 18, GUI.shadingStyleMenu.val,
1941 "Choose the shading style")
1945 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1946 200, 235, 160, 18, GUI.showEdgesToggle.val,
1947 "Render polygon edges")
1949 if GUI.showEdgesToggle.val == 1:
1952 edgeStyleMenuStruct = "Edge Style %t"
1953 for t in edgeStyles.keys():
1954 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
1955 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1956 200, 210, 160, 18, GUI.edgeStyleMenu.val,
1957 "Choose the edge style")
1960 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1961 200, 185, 140, 18, GUI.edgeWidthSlider.val,
1962 0.0, 10.0, 0, "Change Edge Width")
1965 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
1966 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
1969 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1970 GUI.evtShowHiddenEdgesToggle,
1971 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
1972 "Render hidden edges as dashed lines")
1974 glRasterPos2i(10, 160)
1975 Draw.Text("%s (c) 2006" % __author__)
1977 def event(evt, val):
1979 if evt == Draw.ESCKEY or evt == Draw.QKEY:
1986 def button_event(evt):
1988 if evt == GUI.evtExitButton:
1991 elif evt == GUI.evtOutFormatMenu:
1992 i = GUI.outFormatMenu.val - 1
1993 config.output['FORMAT']= outputWriters.keys()[i]
1995 elif evt == GUI.evtAnimToggle:
1996 config.output['ANIMATION'] = bool(GUI.animToggle.val)
1998 elif evt == GUI.evtJoinObjsToggle:
1999 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
2001 elif evt == GUI.evtPolygonsToggle:
2002 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
2004 elif evt == GUI.evtShadingStyleMenu:
2005 i = GUI.shadingStyleMenu.val - 1
2006 config.polygons['SHADING'] = shadingStyles.keys()[i]
2008 elif evt == GUI.evtShowEdgesToggle:
2009 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
2011 elif evt == GUI.evtShowHiddenEdgesToggle:
2012 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
2014 elif evt == GUI.evtEdgeStyleMenu:
2015 i = GUI.edgeStyleMenu.val - 1
2016 config.edges['STYLE'] = edgeStyles.keys()[i]
2018 elif evt == GUI.evtEdgeWidthSlider:
2019 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
2021 elif evt == GUI.evtEdgeColorPicker:
2022 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
2024 elif evt == GUI.evtRenderButton:
2025 label = "Save %s" % config.output['FORMAT']
2026 # Show the File Selector
2028 Blender.Window.FileSelector(vectorize, label, outputfile)
2031 print "Event: %d not handled!" % evt
2038 from pprint import pprint
2040 pprint(config.output)
2041 pprint(config.polygons)
2042 pprint(config.edges)
2044 _init = staticmethod(_init)
2045 draw = staticmethod(draw)
2046 event = staticmethod(event)
2047 button_event = staticmethod(button_event)
2048 conf_debug = staticmethod(conf_debug)
2050 # A wrapper function for the vectorizing process
2051 def vectorize(filename):
2052 """The vectorizing process is as follows:
2054 - Instanciate the writer and the renderer
2059 print "\nERROR: invalid file name!"
2062 from Blender import Window
2063 editmode = Window.EditMode()
2064 if editmode: Window.EditMode(0)
2066 actualWriter = outputWriters[config.output['FORMAT']]
2067 writer = actualWriter(filename)
2069 renderer = Renderer()
2070 renderer.doRendering(writer, config.output['ANIMATION'])
2072 if editmode: Window.EditMode(1)
2074 # We use a global progress Indicator Object
2078 if __name__ == "__main__":
2083 basename = Blender.sys.basename(Blender.Get('filename'))
2085 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
2087 if Blender.mode == 'background':
2088 progress = ConsoleProgressIndicator()
2089 vectorize(outputfile)
2091 progress = GraphicalProgressIndicator()
2092 Draw.Register(GUI.draw, GUI.event, GUI.button_event)