6 Tooltip: 'Vector Rendering Method script'
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://vrm.projects.blender.org"]
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 # - Switch to the Mesh structure, should be considerably faster
46 # (partially done, but with Mesh we cannot sort faces, yet)
47 # - Use a better depth sorting algorithm
48 # - Review how selections are made (this script uses selection states of
49 # primitives to represent visibility infos)
50 # - Implement clipping of primitives and do handle object intersections.
51 # (for now only clipping for whole objects is supported).
52 # - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
53 # - Implement Edge coloring
54 # - Use multiple lighting sources in color calculation
55 # - Implement Shading Styles? (for now we use Flat Shading).
56 # - Use a data structure other than Mesh to represent the 2D image?
57 # Think to a way to merge adjacent polygons that have the same color.
58 # Or a way to use paths for silhouettes and contours.
59 # - Add Vector Writers other that SVG.
60 # - Consider SMIL for animation handling instead of ECMA Script?
62 # ---------------------------------------------------------------------
66 # vrm-0.3.py - 2006-05-19
67 # * First release after code restucturing.
68 # Now the script offers a useful set of functionalities
69 # and it can render animations, too.
71 # ---------------------------------------------------------------------
74 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera
75 from Blender.Mathutils import *
79 # Some global settings
83 POLYGON_EXPANSION_TRICK = True # Hidden to the user for now
86 SHOW_HIDDEN_EDGES = False
87 EDGE_STYLE = 'silhouette'
90 RENDER_ANIMATION = False
92 OPTIMIZE_FOR_SPACE = True
98 # ---------------------------------------------------------------------
100 ## Utility Mesh class
102 # ---------------------------------------------------------------------
105 def getEdgeAdjacentFaces(edge, mesh):
106 """Get the faces adjacent to a given edge.
108 There can be 0, 1 or more (usually 2) faces adjacent to an edge.
113 if (edge.v1 in f.v) and (edge.v2 in f.v):
114 adjface_list.append(f)
118 def isVisibleEdge(e, mesh):
119 """Normal edge selection rule.
121 An edge is visible if _any_ of its adjacent faces is selected.
122 Note: if the edge has no adjacent faces we want to show it as well,
123 useful for "edge only" portion of objects.
126 adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
128 if len(adjacent_faces) == 0:
131 selected_faces = [f for f in adjacent_faces if f.sel]
133 if len(selected_faces) != 0:
138 def isSilhouetteEdge(e, mesh):
139 """Silhuette selection rule.
141 An edge is a silhuette edge if it is shared by two faces with
142 different selection status or if it is a boundary edge of a selected
146 adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
148 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
149 (len(adjacent_faces) == 2 and
150 adjacent_faces[0].sel != adjacent_faces[1].sel)
156 getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces)
157 isVisibleEdge = staticmethod(isVisibleEdge)
158 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
162 # ---------------------------------------------------------------------
164 ## Projections classes
166 # ---------------------------------------------------------------------
169 """Calculate the projection of an object given the camera.
171 A projector is useful to so some per-object transformation to obtain the
172 projection of an object given the camera.
174 The main method is #doProjection# see the method description for the
178 def __init__(self, cameraObj, canvasRatio):
179 """Calculate the projection matrix.
181 The projection matrix depends, in this case, on the camera settings.
182 TAKE CARE: This projector expects vertices in World Coordinates!
185 camera = cameraObj.getData()
187 aspect = float(canvasRatio[0])/float(canvasRatio[1])
188 near = camera.clipStart
191 scale = float(camera.scale)
193 fovy = atan(0.5/aspect/(camera.lens/32))
194 fovy = fovy * 360.0/pi
196 # What projection do we want?
198 #mP = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale)
199 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
201 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
203 # View transformation
204 cam = Matrix(cameraObj.getInverseMatrix())
209 self.projectionMatrix = mP
215 def doProjection(self, v):
216 """Project the point on the view plane.
218 Given a vertex calculate the projection using the current projection
222 # Note that we have to work on the vertex using homogeneous coordinates
223 p = self.projectionMatrix * Vector(v).resize4D()
239 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
240 """Return a perspective projection matrix.
243 top = near * tan(fovy * pi / 360.0)
247 x = (2.0 * near) / (right-left)
248 y = (2.0 * near) / (top-bottom)
249 a = (right+left) / (right-left)
250 b = (top+bottom) / (top - bottom)
251 c = - ((far+near) / (far-near))
252 d = - ((2*far*near)/(far-near))
258 [0.0, 0.0, -1.0, 0.0])
262 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
263 """Return an orthogonal projection matrix.
266 # The 11 in the formula was found emiprically
267 top = near * tan(fovy * pi / 360.0) * (scale * 11)
269 left = bottom * aspect
274 tx = -((right+left)/rl)
275 ty = -((top+bottom)/tb)
279 [2.0/rl, 0.0, 0.0, tx],
280 [0.0, 2.0/tb, 0.0, ty],
281 [0.0, 0.0, 2.0/fn, tz],
282 [0.0, 0.0, 0.0, 1.0])
288 # ---------------------------------------------------------------------
290 ## 2D Object representation class
292 # ---------------------------------------------------------------------
294 # TODO: a class to represent the needed properties of a 2D vector image
295 # For now just using a [N]Mesh structure.
298 # ---------------------------------------------------------------------
300 ## Vector Drawing Classes
302 # ---------------------------------------------------------------------
308 A class for printing output in a vectorial format.
310 Given a 2D representation of the 3D scene the class is responsible to
311 write it is a vector format.
313 Every subclasses of VectorWriter must have at last the following public
317 - printCanvas(self, scene,
318 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
321 def __init__(self, fileName):
322 """Set the output file name and other properties"""
324 self.outputFileName = fileName
327 context = Scene.GetCurrent().getRenderingContext()
328 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
332 self.animation = False
339 def open(self, startFrame=1, endFrame=1):
340 if startFrame != endFrame:
341 self.startFrame = startFrame
342 self.endFrame = endFrame
343 self.animation = True
345 self.file = open(self.outputFileName, "w")
346 print "Outputting to: ", self.outputFileName
354 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
355 showHiddenEdges=False):
356 """This is the interface for the needed printing routine.
363 class SVGVectorWriter(VectorWriter):
364 """A concrete class for writing SVG output.
367 def __init__(self, fileName):
368 """Simply call the parent Contructor.
370 VectorWriter.__init__(self, fileName)
377 def open(self, startFrame=1, endFrame=1):
378 """Do some initialization operations.
380 VectorWriter.open(self, startFrame, endFrame)
384 """Do some finalization operation.
388 # remember to call the close method of the parent
389 VectorWriter.close(self)
392 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
393 showHiddenEdges=False):
394 """Convert the scene representation to SVG.
397 Objects = scene.getChildren()
399 context = scene.getRenderingContext()
400 framenumber = context.currentFrame()
403 framestyle = "display:none"
405 framestyle = "display:block"
407 # Assign an id to this group so we can set properties on it using DOM
408 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
409 (framenumber, framestyle) )
413 if(obj.getType() != 'Mesh'):
416 self.file.write("<g id=\"%s\">\n" % obj.getName())
418 mesh = obj.getData(mesh=1)
421 self._printPolygons(mesh)
424 self._printEdges(mesh, showHiddenEdges)
426 self.file.write("</g>\n")
428 self.file.write("</g>\n")
435 def _calcCanvasCoord(self, v):
436 """Convert vertex in scene coordinates to canvas coordinates.
439 pt = Vector([0, 0, 0])
441 mW = float(self.canvasSize[0])/2.0
442 mH = float(self.canvasSize[1])/2.0
444 # rescale to canvas size
445 pt[0] = v.co[0]*mW + mW
446 pt[1] = v.co[1]*mH + mH
449 # For now we want (0,0) in the top-left corner of the canvas.
450 # Mirror and translate along y
452 pt[1] += self.canvasSize[1]
456 def _printHeader(self):
457 """Print SVG header."""
459 self.file.write("<?xml version=\"1.0\"?>\n")
460 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n")
461 self.file.write("\t\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
462 self.file.write("<svg version=\"1.1\"\n")
463 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
464 self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
469 self.file.write("""\n<script><![CDATA[
473 /* FIXME: Use 1000 as interval as lower values gives problems */
474 timerID = setInterval("NextFrame()", 1000);
475 globalFrameCounter=%d;
479 currentElement = document.getElementById('frame'+globalFrameCounter)
480 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
487 if (globalFrameCounter > globalEndFrame)
489 clearInterval(timerID)
495 previousElement.style.display="none";
497 currentElement.style.display="block";
498 globalFrameCounter++;
502 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
504 def _printFooter(self):
505 """Print the SVG footer."""
507 self.file.write("\n</svg>\n")
509 def _printPolygons(self, mesh):
510 """Print the selected (visible) polygons.
513 if len(mesh.faces) == 0:
516 self.file.write("<g>\n")
518 for face in mesh.faces:
522 self.file.write("<polygon points=\"")
525 p = self._calcCanvasCoord(v)
526 self.file.write("%g,%g " % (p[0], p[1]))
528 # get rid of the last blank space, just cosmetics here.
529 self.file.seek(-1, 1)
530 self.file.write("\"\n")
532 # take as face color the first vertex color
533 # TODO: the average of vetrex colors?
536 color = [fcol.r, fcol.g, fcol.b, fcol.a]
538 color = [255, 255, 255, 255]
540 # use the stroke property to alleviate the "adjacent edges" problem,
541 # we simulate polygon expansion using borders,
542 # see http://www.antigrain.com/svg/index.html for more info
546 # Convert the color to the #RRGGBB form
547 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
549 # Handle transparent polygons
552 opacity = float(color[3])/255.0
553 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
555 self.file.write("\tstyle=\"fill:" + str_col + ";")
556 self.file.write(opacity_string)
557 if POLYGON_EXPANSION_TRICK:
558 self.file.write(" stroke:" + str_col + ";")
559 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
560 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
561 self.file.write("\"/>\n")
563 self.file.write("</g>\n")
565 def _printEdges(self, mesh, showHiddenEdges=False):
566 """Print the wireframe using mesh edges.
569 stroke_width=EDGES_WIDTH
570 stroke_col = [0, 0, 0]
572 self.file.write("<g>\n")
576 hidden_stroke_style = ""
578 # We consider an edge visible if _both_ its vertices are selected,
579 # hence an edge is hidden if _any_ of its vertices is deselected.
581 if showHiddenEdges == False:
584 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
586 p1 = self._calcCanvasCoord(e.v1)
587 p2 = self._calcCanvasCoord(e.v2)
589 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
590 % ( p1[0], p1[1], p2[0], p2[1] ) )
591 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
592 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
593 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
594 self.file.write(hidden_stroke_style)
595 self.file.write("\"/>\n")
597 self.file.write("</g>\n")
601 # ---------------------------------------------------------------------
605 # ---------------------------------------------------------------------
607 # A dictionary to collect all the different edge styles and their edge
609 edgeSelectionStyles = {
610 'normal': MeshUtils.isVisibleEdge,
611 'silhouette': MeshUtils.isSilhouetteEdge
614 # A dictionary to collect the supported output formats
616 'SVG': SVGVectorWriter,
621 """Render a scene viewed from a given camera.
623 This class is responsible of the rendering process, transformation and
624 projection of the objects in the scene are invoked by the renderer.
626 The rendering is done using the active camera for the current scene.
630 """Make the rendering process only for the current scene by default.
632 We will work on a copy of the scene, be sure that the current scene do
633 not get modified in any way.
636 # Render the current Scene, this should be a READ-ONLY property
637 self._SCENE = Scene.GetCurrent()
639 # Use the aspect ratio of the scene rendering context
640 context = self._SCENE.getRenderingContext()
642 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
643 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
644 float(context.aspectRatioY())
647 # Render from the currently active camera
648 self.cameraObj = self._SCENE.getCurrentCamera()
650 # Get the list of lighting sources
651 obj_lst = self._SCENE.getChildren()
652 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
654 # When there are no lights we use a default lighting source
655 # that have the same position of the camera
656 if len(self.lights) == 0:
658 lobj = Object.New('Lamp')
659 lobj.loc = self.cameraObj.loc
661 self.lights.append(lobj)
668 def doRendering(self, outputWriter, animation=False):
669 """Render picture or animation and write it out.
672 - a Vector writer object that will be used to output the result.
673 - a flag to tell if we want to render an animation or only the
677 context = self._SCENE.getRenderingContext()
678 currentFrame = context.currentFrame()
680 # Handle the animation case
682 startFrame = currentFrame
683 endFrame = startFrame
686 startFrame = context.startFrame()
687 endFrame = context.endFrame()
688 outputWriter.open(startFrame, endFrame)
690 # Do the rendering process frame by frame
691 print "Start Rendering!"
692 for f in range(startFrame, endFrame+1):
693 context.currentFrame(f)
695 renderedScene = self.doRenderScene(self._SCENE)
696 outputWriter.printCanvas(renderedScene,
697 doPrintPolygons = PRINT_POLYGONS,
698 doPrintEdges = PRINT_EDGES,
699 showHiddenEdges = SHOW_HIDDEN_EDGES)
701 # clear the rendered scene
702 self._SCENE.makeCurrent()
703 Scene.unlink(renderedScene)
708 context.currentFrame(currentFrame)
711 def doRenderScene(self, inputScene):
712 """Control the rendering process.
714 Here we control the entire rendering process invoking the operation
715 needed to transform and project the 3D scene in two dimensions.
718 # Use some temporary workspace, a full copy of the scene
719 workScene = inputScene.copy(2)
721 # Get a projector for this scene.
722 # NOTE: the projector wants object in world coordinates,
723 # so we should apply modelview transformations _before_
724 # projection transformations
725 proj = Projector(self.cameraObj, self.canvasRatio)
727 # global processing of the scene
729 self._doConvertGeometricObjToMesh(workScene)
731 self._doSceneClipping(workScene)
734 # XXX: Joining objects does not work in batch mode!!
735 # Do not touch the following if, please :)
737 global OPTIMIZE_FOR_SPACE
738 if Blender.mode == 'background':
739 print "\nWARNING! Joining objects not supported in background mode!\n"
740 OPTIMIZE_FOR_SPACE = False
742 if OPTIMIZE_FOR_SPACE:
743 self._joinMeshObjectsInScene(workScene)
746 self._doSceneDepthSorting(workScene)
748 # Per object activities
750 Objects = workScene.getChildren()
753 if obj.getType() != 'Mesh':
754 print "Only Mesh supported! - Skipping type:", obj.getType()
757 print "Rendering: ", obj.getName()
761 self._doModelToWorldCoordinates(mesh, obj.matrix)
763 self._doObjectDepthSorting(mesh)
765 # We use both Mesh and NMesh because for depth sorting we change
766 # face order and Mesh class don't let us to do that.
768 mesh = obj.getData(mesh=1)
770 self._doBackFaceCulling(mesh)
772 self._doColorAndLighting(mesh)
774 self._doEdgesStyle(mesh, edgeSelectionStyles[EDGE_STYLE])
776 self._doProjection(mesh, proj)
778 # Update the object data, important! :)
790 def _getObjPosition(self, obj):
791 """Return the obj position in World coordinates.
793 return obj.matrix.translationPart()
795 def _cameraViewDirection(self):
796 """Get the View Direction form the camera matrix.
798 return Vector(self.cameraObj.matrix[2]).resize3D()
803 def _isFaceVisible(self, face):
804 """Determine if a face of an object is visible from the current camera.
806 The view vector is calculated from the camera location and one of the
807 vertices of the face (expressed in World coordinates, after applying
808 modelview transformations).
810 After those transformations we determine if a face is visible by
811 computing the angle between the face normal and the view vector, this
812 angle has to be between -90 and 90 degrees for the face to be visible.
813 This corresponds somehow to the dot product between the two, if it
814 results > 0 then the face is visible.
816 There is no need to normalize those vectors since we are only interested in
817 the sign of the cross product and not in the product value.
819 NOTE: here we assume the face vertices are in WorldCoordinates, so
820 please transform the object _before_ doing the test.
823 normal = Vector(face.no)
824 camPos = self._getObjPosition(self.cameraObj)
827 # View Vector in orthographics projections is the view Direction of
829 if self.cameraObj.data.getType() == 1:
830 view_vect = self._cameraViewDirection()
832 # View vector in perspective projections can be considered as
833 # the difference between the camera position and one point of
834 # the face, we choose the farthest point from the camera.
835 if self.cameraObj.data.getType() == 0:
836 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
839 # if d > 0 the face is visible from the camera
840 d = view_vect * normal
850 def _doConvertGeometricObjToMesh(self, scene):
851 """Convert all "geometric" objects to mesh ones.
853 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
855 Objects = scene.getChildren()
856 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
859 obj = self._convertToRawMeshObj(obj)
861 scene.unlink(old_obj)
864 # XXX Workaround for Text and Curve which have some normals
865 # inverted when they are converted to Mesh, REMOVE that when
866 # blender will fix that!!
867 if old_obj.getType() in ['Curve', 'Text']:
868 me = obj.getData(mesh=1)
869 for f in me.faces: f.sel = 1;
870 for v in me.verts: v.sel = 1;
877 def _doSceneClipping(self, scene):
878 """Clip objects against the View Frustum.
880 For now clip away only objects according to their center position.
883 cpos = self._getObjPosition(self.cameraObj)
884 view_vect = self._cameraViewDirection()
886 near = self.cameraObj.data.clipStart
887 far = self.cameraObj.data.clipEnd
889 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
890 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
891 fovy = fovy * 360.0/pi
893 Objects = scene.getChildren()
895 if o.getType() != 'Mesh': continue;
897 obj_vect = Vector(cpos) - self._getObjPosition(o)
899 d = obj_vect*view_vect
900 theta = AngleBetweenVecs(obj_vect, view_vect)
902 # if the object is outside the view frustum, clip it away
903 if (d < near) or (d > far) or (theta > fovy):
906 def _doSceneDepthSorting(self, scene):
907 """Sort objects in the scene.
909 The object sorting is done accordingly to the object centers.
912 c = self._getObjPosition(self.cameraObj)
914 by_center_pos = (lambda o1, o2:
915 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
916 cmp((self._getObjPosition(o1) - Vector(c)).length,
917 (self._getObjPosition(o2) - Vector(c)).length)
920 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
921 # then ob1 goes farther than obj2, useful when obj2 has holes
924 Objects = scene.getChildren()
925 Objects.sort(by_center_pos)
932 def _joinMeshObjectsInScene(self, scene):
933 """Merge all the Mesh Objects in a scene into a single Mesh Object.
936 bigObj = Object.New('Mesh', 'BigOne')
939 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
950 def _convertToRawMeshObj(self, object):
951 """Convert geometry based object to a mesh object.
953 me = Mesh.New('RawMesh_'+object.name)
954 me.getFromObject(object.name)
956 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
959 # If the object has no materials set a default material
961 me.materials = [Material.New()]
962 #for f in me.faces: f.mat = 0
964 newObject.setMatrix(object.getMatrix())
968 def _doModelToWorldCoordinates(self, mesh, matrix):
969 """Transform object coordinates to world coordinates.
971 This step is done simply applying to the object its tranformation
972 matrix and recalculating its normals.
974 mesh.transform(matrix, True)
976 def _doObjectDepthSorting(self, mesh):
977 """Sort faces in an object.
979 The faces in the object are sorted following the distance of the
980 vertices from the camera position.
982 c = self._getObjPosition(self.cameraObj)
984 # hackish sorting of faces
986 # Sort faces according to the max distance from the camera
987 by_max_vert_dist = (lambda f1, f2:
988 cmp(max([(Vector(v.co)-Vector(c)).length for v in f1]),
989 max([(Vector(v.co)-Vector(c)).length for v in f2])))
991 # Sort faces according to the min distance from the camera
992 by_min_vert_dist = (lambda f1, f2:
993 cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
994 min([(Vector(v.co)-Vector(c)).length for v in f2])))
996 # Sort faces according to the avg distance from the camera
997 by_avg_vert_dist = (lambda f1, f2:
998 cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
999 sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
1001 mesh.faces.sort(by_max_vert_dist)
1002 mesh.faces.reverse()
1004 def _doBackFaceCulling(self, mesh):
1005 """Simple Backface Culling routine.
1007 At this level we simply do a visibility test face by face and then
1008 select the vertices belonging to visible faces.
1011 # Select all vertices, so edges can be displayed even if there are no
1013 for v in mesh.verts:
1016 Mesh.Mode(Mesh.SelectModes['FACE'])
1018 for f in mesh.faces:
1020 if self._isFaceVisible(f):
1023 # Is this the correct way to propagate the face selection info to the
1024 # vertices belonging to a face ??
1025 # TODO: Using the Mesh module this should come for free. Right?
1026 #Mesh.Mode(Mesh.SelectModes['VERTEX'])
1027 #for f in mesh.faces:
1029 # for v in f: v.sel = 0;
1031 #for f in mesh.faces:
1033 # for v in f: v.sel = 1;
1035 def _doColorAndLighting(self, mesh):
1036 """Apply an Illumination model to the object.
1038 The Illumination model used is the Phong one, it may be inefficient,
1039 but I'm just learning about rendering and starting from Phong seemed
1040 the most natural way.
1043 # If the mesh has vertex colors already, use them,
1044 # otherwise turn them on and do some calculations
1045 if mesh.vertexColors:
1047 mesh.vertexColors = 1
1049 materials = mesh.materials
1051 # TODO: use multiple lighting sources
1052 light_obj = self.lights[0]
1053 light_pos = self._getObjPosition(light_obj)
1054 light = light_obj.data
1056 camPos = self._getObjPosition(self.cameraObj)
1058 # We do per-face color calculation (FLAT Shading), we can easily turn
1059 # to a per-vertex calculation if we want to implement some shading
1060 # technique. For an example see:
1061 # http://www.miralab.unige.ch/papers/368.pdf
1062 for f in mesh.faces:
1068 mat = materials[f.mat]
1070 # A new default material
1072 mat = Material.New('defMat')
1074 L = Vector(light_pos).normalize()
1076 V = (Vector(camPos) - Vector(f.v[0].co)).normalize()
1078 N = Vector(f.no).normalize()
1080 R = 2 * (N*L) * N - L
1082 # TODO: Attenuation factor (not used for now)
1083 a0 = 1; a1 = 0.0; a2 = 0.0
1084 d = (Vector(f.v[0].co) - Vector(light_pos)).length
1085 fd = min(1, 1.0/(a0 + a1*d + a2*d*d))
1089 ka = mat.getAmb() * Vector([0.1, 0.1, 0.1])
1092 # Diffuse component (add light.col for kd)
1093 kd = mat.getRef() * Vector(mat.getRGBCol())
1094 Ip = light.getEnergy()
1095 Idiff = Ip * kd * (N*L)
1097 # Specular component
1098 ks = mat.getSpec() * Vector(mat.getSpecCol())
1099 ns = mat.getHardness()
1100 Ispec = Ip * ks * pow((V * R), ns)
1102 # Emissive component
1103 ki = Vector([mat.getEmit()]*3)
1105 I = ki + Iamb + Idiff + Ispec
1107 # Set Alpha component
1109 I.append(mat.getAlpha())
1111 # Clamp I values between 0 and 1
1112 I = [ min(c, 1) for c in I]
1113 I = [ max(0, c) for c in I]
1114 tmp_col = [ int(c * 255.0) for c in I]
1122 def _doEdgesStyle(self, mesh, edgestyleSelect):
1123 """Process Mesh Edges accroding to a given selection style.
1125 Examples of algorithms:
1128 given an edge if its adjacent faces have the same normal (that is
1129 they are complanar), than deselect it.
1132 given an edge if one its adjacent faces is frontfacing and the
1133 other is backfacing, than select it, else deselect.
1136 Mesh.Mode(Mesh.SelectModes['EDGE'])
1138 for e in mesh.edges:
1140 if edgestyleSelect(e, mesh):
1145 def _doProjection(self, mesh, projector):
1146 """Calculate the Projection for the object.
1148 # TODO: maybe using the object.transform() can be faster?
1150 for v in mesh.verts:
1151 p = projector.doProjection(v.co)
1158 # ---------------------------------------------------------------------
1160 ## GUI Class and Main Program
1162 # ---------------------------------------------------------------------
1165 from Blender import BGL, Draw
1166 from Blender.BGL import *
1172 # Output Format menu
1173 default_value = outputWriters.keys().index(OUTPUT_FORMAT)+1
1174 GUI.outFormatMenu = Draw.Create(default_value)
1175 GUI.evtOutFormatMenu = 0
1177 # Animation toggle button
1178 GUI.animToggle = Draw.Create(RENDER_ANIMATION)
1179 GUI.evtAnimToggle = 1
1181 # Join Objects toggle button
1182 GUI.joinObjsToggle = Draw.Create(OPTIMIZE_FOR_SPACE)
1183 GUI.evtJoinObjsToggle = 2
1185 # Render filled polygons
1186 GUI.polygonsToggle = Draw.Create(PRINT_POLYGONS)
1187 GUI.evtPolygonsToggle = 3
1188 # We hide the POLYGON_EXPANSION_TRICK, for now
1190 # Render polygon edges
1191 GUI.showEdgesToggle = Draw.Create(PRINT_EDGES)
1192 GUI.evtShowEdgesToggle = 4
1194 # Render hidden edges
1195 GUI.showHiddenEdgesToggle = Draw.Create(SHOW_HIDDEN_EDGES)
1196 GUI.evtShowHiddenEdgesToggle = 5
1199 default_value = edgeSelectionStyles.keys().index(EDGE_STYLE)+1
1200 GUI.edgeStyleMenu = Draw.Create(default_value)
1201 GUI.evtEdgeStyleMenu = 6
1204 GUI.edgeWidthSlider = Draw.Create(EDGES_WIDTH)
1205 GUI.evtEdgeWidthSlider = 7
1208 GUI.evtRenderButton = 8
1211 GUI.evtExitButton = 9
1215 # initialize static members
1218 glClear(GL_COLOR_BUFFER_BIT)
1219 glColor3f(0.0, 0.0, 0.0)
1220 glRasterPos2i(10, 350)
1221 Draw.Text("VRM: Vector Rendering Method script.")
1222 glRasterPos2i(10, 335)
1223 Draw.Text("Press Q or ESC to quit.")
1225 # Build the output format menu
1226 glRasterPos2i(10, 310)
1227 Draw.Text("Select the output Format:")
1228 outMenuStruct = "Output Format %t"
1229 for t in outputWriters.keys():
1230 outMenuStruct = outMenuStruct + "|%s" % t
1231 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1232 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1235 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1236 10, 260, 160, 18, GUI.animToggle.val,
1237 "Toggle rendering of animations")
1239 # Join Objects toggle
1240 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1241 10, 235, 160, 18, GUI.joinObjsToggle.val,
1242 "Join objects in the rendered file")
1245 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1247 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1250 glRasterPos2i(200, 310)
1251 Draw.Text("Rendering Style:")
1254 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1255 200, 285, 160, 18, GUI.polygonsToggle.val,
1256 "Render filled polygons")
1259 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1260 200, 260, 160, 18, GUI.showEdgesToggle.val,
1261 "Render polygon edges")
1263 if GUI.showEdgesToggle.val == 1:
1266 edgeStyleMenuStruct = "Edge Style %t"
1267 for t in edgeSelectionStyles.keys():
1268 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t
1269 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1270 200, 235, 160, 18, GUI.edgeStyleMenu.val,
1271 "Choose the edge style")
1274 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1275 200, 210, 160, 18, GUI.edgeWidthSlider.val,
1276 0.0, 10.0, 0, "Change Edge Width")
1279 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1280 GUI.evtShowHiddenEdgesToggle,
1281 200, 185, 160, 18, GUI.showHiddenEdgesToggle.val,
1282 "Render hidden edges as dashed lines")
1284 glRasterPos2i(10, 160)
1285 Draw.Text("Antonio Ospite (c) 2006")
1287 def event(evt, val):
1289 if evt == Draw.ESCKEY or evt == Draw.QKEY:
1296 def button_event(evt):
1297 global PRINT_POLYGONS
1298 global POLYGON_EXPANSION_TRICK
1300 global SHOW_HIDDEN_EDGES
1303 global RENDER_ANIMATION
1304 global OPTIMIZE_FOR_SPACE
1305 global OUTPUT_FORMAT
1307 if evt == GUI.evtExitButton:
1309 elif evt == GUI.evtOutFormatMenu:
1310 i = GUI.outFormatMenu.val - 1
1311 OUTPUT_FORMAT = outputWriters.keys()[i]
1312 elif evt == GUI.evtAnimToggle:
1313 RENDER_ANIMATION = bool(GUI.animToggle.val)
1314 elif evt == GUI.evtJoinObjsToggle:
1315 OPTIMIZE_FOR_SPACE = bool(GUI.joinObjsToggle.val)
1316 elif evt == GUI.evtPolygonsToggle:
1317 PRINT_POLYGONS = bool(GUI.polygonsToggle.val)
1318 elif evt == GUI.evtShowEdgesToggle:
1319 PRINT_EDGES = bool(GUI.showEdgesToggle.val)
1320 elif evt == GUI.evtShowHiddenEdgesToggle:
1321 SHOW_HIDDEN_EDGES = bool(GUI.showHiddenEdgesToggle.val)
1322 elif evt == GUI.evtEdgeStyleMenu:
1323 i = GUI.edgeStyleMenu.val - 1
1324 EDGE_STYLE = edgeSelectionStyles.keys()[i]
1325 elif evt == GUI.evtEdgeWidthSlider:
1326 EDGES_WIDTH = float(GUI.edgeWidthSlider.val)
1327 elif evt == GUI.evtRenderButton:
1328 label = "Save %s" % OUTPUT_FORMAT
1329 # Show the File Selector
1331 Blender.Window.FileSelector(vectorize, label, outputfile)
1334 print "Event: %d not handled!" % evt
1342 print "PRINT_POLYGONS:", PRINT_POLYGONS
1343 print "POLYGON_EXPANSION_TRICK:", POLYGON_EXPANSION_TRICK
1344 print "PRINT_EDGES:", PRINT_EDGES
1345 print "SHOW_HIDDEN_EDGES:", SHOW_HIDDEN_EDGES
1346 print "EDGE_STYLE:", EDGE_STYLE
1347 print "EDGES_WIDTH:", EDGES_WIDTH
1348 print "RENDER_ANIMATION:", RENDER_ANIMATION
1349 print "OPTIMIZE_FOR_SPACE:", OPTIMIZE_FOR_SPACE
1350 print "OUTPUT_FORMAT:", OUTPUT_FORMAT
1352 _init = staticmethod(_init)
1353 draw = staticmethod(draw)
1354 event = staticmethod(event)
1355 button_event = staticmethod(button_event)
1356 conf_debug = staticmethod(conf_debug)
1358 # A wrapper function for the vectorizing process
1359 def vectorize(filename):
1360 """The vectorizing process is as follows:
1362 - Instanciate the writer and the renderer
1367 print "\nERROR: invalid file name!"
1370 from Blender import Window
1371 editmode = Window.EditMode()
1372 if editmode: Window.EditMode(0)
1374 actualWriter = outputWriters[OUTPUT_FORMAT]
1375 writer = actualWriter(filename)
1377 renderer = Renderer()
1378 renderer.doRendering(writer, RENDER_ANIMATION)
1380 if editmode: Window.EditMode(1)
1384 if __name__ == "__main__":
1387 basename = Blender.sys.basename(Blender.Get('filename'))
1389 outputfile = Blender.sys.splitext(basename)[0] + "." + str(OUTPUT_FORMAT).lower()
1391 if Blender.mode == 'background':
1392 vectorize(outputfile)
1394 Draw.Register(GUI.draw, GUI.event, GUI.button_event)