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? (Firefox do
61 # not support SMIL for animations)
62 # - FIX the issue with negative scales in object tranformations!
64 # ---------------------------------------------------------------------
68 # vrm-0.3.py - 2006-05-19
69 # * First release after code restucturing.
70 # Now the script offers a useful set of functionalities
71 # and it can render animations, too.
73 # ---------------------------------------------------------------------
76 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera
77 from Blender.Mathutils import *
81 # Some global settings
85 POLYGON_EXPANSION_TRICK = True # Hidden to the user for now
88 SHOW_HIDDEN_EDGES = False
89 EDGE_STYLE = 'silhouette'
92 RENDER_ANIMATION = False
94 OPTIMIZE_FOR_SPACE = True
100 # ---------------------------------------------------------------------
102 ## Utility Mesh class
104 # ---------------------------------------------------------------------
107 def getEdgeAdjacentFaces(edge, mesh):
108 """Get the faces adjacent to a given edge.
110 There can be 0, 1 or more (usually 2) faces adjacent to an edge.
115 if (edge.v1 in f.v) and (edge.v2 in f.v):
116 adjface_list.append(f)
120 def isVisibleEdge(e, mesh):
121 """Normal edge selection rule.
123 An edge is visible if _any_ of its adjacent faces is selected.
124 Note: if the edge has no adjacent faces we want to show it as well,
125 useful for "edge only" portion of objects.
128 adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
130 if len(adjacent_faces) == 0:
133 selected_faces = [f for f in adjacent_faces if f.sel]
135 if len(selected_faces) != 0:
140 def isSilhouetteEdge(e, mesh):
141 """Silhuette selection rule.
143 An edge is a silhuette edge if it is shared by two faces with
144 different selection status or if it is a boundary edge of a selected
148 adjacent_faces = MeshUtils.getEdgeAdjacentFaces(e, mesh)
150 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
151 (len(adjacent_faces) == 2 and
152 adjacent_faces[0].sel != adjacent_faces[1].sel)
158 getEdgeAdjacentFaces = staticmethod(getEdgeAdjacentFaces)
159 isVisibleEdge = staticmethod(isVisibleEdge)
160 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
164 # ---------------------------------------------------------------------
166 ## Projections classes
168 # ---------------------------------------------------------------------
171 """Calculate the projection of an object given the camera.
173 A projector is useful to so some per-object transformation to obtain the
174 projection of an object given the camera.
176 The main method is #doProjection# see the method description for the
180 def __init__(self, cameraObj, canvasRatio):
181 """Calculate the projection matrix.
183 The projection matrix depends, in this case, on the camera settings.
184 TAKE CARE: This projector expects vertices in World Coordinates!
187 camera = cameraObj.getData()
189 aspect = float(canvasRatio[0])/float(canvasRatio[1])
190 near = camera.clipStart
193 scale = float(camera.scale)
195 fovy = atan(0.5/aspect/(camera.lens/32))
196 fovy = fovy * 360.0/pi
198 # What projection do we want?
200 #mP = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale)
201 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
203 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
205 # View transformation
206 cam = Matrix(cameraObj.getInverseMatrix())
211 self.projectionMatrix = mP
217 def doProjection(self, v):
218 """Project the point on the view plane.
220 Given a vertex calculate the projection using the current projection
224 # Note that we have to work on the vertex using homogeneous coordinates
225 p = self.projectionMatrix * Vector(v).resize4D()
241 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
242 """Return a perspective projection matrix.
245 top = near * tan(fovy * pi / 360.0)
249 x = (2.0 * near) / (right-left)
250 y = (2.0 * near) / (top-bottom)
251 a = (right+left) / (right-left)
252 b = (top+bottom) / (top - bottom)
253 c = - ((far+near) / (far-near))
254 d = - ((2*far*near)/(far-near))
260 [0.0, 0.0, -1.0, 0.0])
264 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
265 """Return an orthogonal projection matrix.
268 # The 11 in the formula was found emiprically
269 top = near * tan(fovy * pi / 360.0) * (scale * 11)
271 left = bottom * aspect
276 tx = -((right+left)/rl)
277 ty = -((top+bottom)/tb)
281 [2.0/rl, 0.0, 0.0, tx],
282 [0.0, 2.0/tb, 0.0, ty],
283 [0.0, 0.0, 2.0/fn, tz],
284 [0.0, 0.0, 0.0, 1.0])
290 # ---------------------------------------------------------------------
292 ## 2D Object representation class
294 # ---------------------------------------------------------------------
296 # TODO: a class to represent the needed properties of a 2D vector image
297 # For now just using a [N]Mesh structure.
300 # ---------------------------------------------------------------------
302 ## Vector Drawing Classes
304 # ---------------------------------------------------------------------
310 A class for printing output in a vectorial format.
312 Given a 2D representation of the 3D scene the class is responsible to
313 write it is a vector format.
315 Every subclasses of VectorWriter must have at last the following public
319 - printCanvas(self, scene,
320 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
323 def __init__(self, fileName):
324 """Set the output file name and other properties"""
326 self.outputFileName = fileName
329 context = Scene.GetCurrent().getRenderingContext()
330 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
334 self.animation = False
341 def open(self, startFrame=1, endFrame=1):
342 if startFrame != endFrame:
343 self.startFrame = startFrame
344 self.endFrame = endFrame
345 self.animation = True
347 self.file = open(self.outputFileName, "w")
348 print "Outputting to: ", self.outputFileName
356 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
357 showHiddenEdges=False):
358 """This is the interface for the needed printing routine.
365 class SVGVectorWriter(VectorWriter):
366 """A concrete class for writing SVG output.
369 def __init__(self, fileName):
370 """Simply call the parent Contructor.
372 VectorWriter.__init__(self, fileName)
379 def open(self, startFrame=1, endFrame=1):
380 """Do some initialization operations.
382 VectorWriter.open(self, startFrame, endFrame)
386 """Do some finalization operation.
390 # remember to call the close method of the parent
391 VectorWriter.close(self)
394 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
395 showHiddenEdges=False):
396 """Convert the scene representation to SVG.
399 Objects = scene.getChildren()
401 context = scene.getRenderingContext()
402 framenumber = context.currentFrame()
405 framestyle = "display:none"
407 framestyle = "display:block"
409 # Assign an id to this group so we can set properties on it using DOM
410 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
411 (framenumber, framestyle) )
416 if(obj.getType() != 'Mesh'):
419 self.file.write("<g id=\"%s\">\n" % obj.getName())
421 mesh = obj.getData(mesh=1)
424 self._printPolygons(mesh)
427 self._printEdges(mesh, showHiddenEdges)
429 self.file.write("</g>\n")
431 self.file.write("</g>\n")
438 def _calcCanvasCoord(self, v):
439 """Convert vertex in scene coordinates to canvas coordinates.
442 pt = Vector([0, 0, 0])
444 mW = float(self.canvasSize[0])/2.0
445 mH = float(self.canvasSize[1])/2.0
447 # rescale to canvas size
448 pt[0] = v.co[0]*mW + mW
449 pt[1] = v.co[1]*mH + mH
452 # For now we want (0,0) in the top-left corner of the canvas.
453 # Mirror and translate along y
455 pt[1] += self.canvasSize[1]
459 def _printHeader(self):
460 """Print SVG header."""
462 self.file.write("<?xml version=\"1.0\"?>\n")
463 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n")
464 self.file.write("\t\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
465 self.file.write("<svg version=\"1.1\"\n")
466 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
467 self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
472 self.file.write("""\n<script><![CDATA[
476 /* FIXME: Use 1000 as interval as lower values gives problems */
477 timerID = setInterval("NextFrame()", 1000);
478 globalFrameCounter=%d;
482 currentElement = document.getElementById('frame'+globalFrameCounter)
483 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
490 if (globalFrameCounter > globalEndFrame)
492 clearInterval(timerID)
498 previousElement.style.display="none";
500 currentElement.style.display="block";
501 globalFrameCounter++;
505 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
507 def _printFooter(self):
508 """Print the SVG footer."""
510 self.file.write("\n</svg>\n")
512 def _printPolygons(self, mesh):
513 """Print the selected (visible) polygons.
516 if len(mesh.faces) == 0:
519 self.file.write("<g>\n")
521 for face in mesh.faces:
525 self.file.write("<polygon points=\"")
528 p = self._calcCanvasCoord(v)
529 self.file.write("%g,%g " % (p[0], p[1]))
531 # get rid of the last blank space, just cosmetics here.
532 self.file.seek(-1, 1)
533 self.file.write("\"\n")
535 # take as face color the first vertex color
536 # TODO: the average of vetrex colors?
539 color = [fcol.r, fcol.g, fcol.b, fcol.a]
541 color = [255, 255, 255, 255]
543 # use the stroke property to alleviate the "adjacent edges" problem,
544 # we simulate polygon expansion using borders,
545 # see http://www.antigrain.com/svg/index.html for more info
549 # Convert the color to the #RRGGBB form
550 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
552 # Handle transparent polygons
555 opacity = float(color[3])/255.0
556 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
558 self.file.write("\tstyle=\"fill:" + str_col + ";")
559 self.file.write(opacity_string)
560 if POLYGON_EXPANSION_TRICK:
561 self.file.write(" stroke:" + str_col + ";")
562 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
563 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
564 self.file.write("\"/>\n")
566 self.file.write("</g>\n")
568 def _printEdges(self, mesh, showHiddenEdges=False):
569 """Print the wireframe using mesh edges.
572 stroke_width=EDGES_WIDTH
573 stroke_col = [0, 0, 0]
575 self.file.write("<g>\n")
579 hidden_stroke_style = ""
582 if showHiddenEdges == False:
585 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
587 p1 = self._calcCanvasCoord(e.v1)
588 p2 = self._calcCanvasCoord(e.v2)
590 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
591 % ( p1[0], p1[1], p2[0], p2[1] ) )
592 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
593 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
594 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
595 self.file.write(hidden_stroke_style)
596 self.file.write("\"/>\n")
598 self.file.write("</g>\n")
602 # ---------------------------------------------------------------------
606 # ---------------------------------------------------------------------
608 # A dictionary to collect all the different edge styles and their edge
610 edgeSelectionStyles = {
611 'normal': MeshUtils.isVisibleEdge,
612 'silhouette': MeshUtils.isSilhouetteEdge
615 # A dictionary to collect the supported output formats
617 'SVG': SVGVectorWriter,
622 """Render a scene viewed from a given camera.
624 This class is responsible of the rendering process, transformation and
625 projection of the objects in the scene are invoked by the renderer.
627 The rendering is done using the active camera for the current scene.
631 """Make the rendering process only for the current scene by default.
633 We will work on a copy of the scene, be sure that the current scene do
634 not get modified in any way.
637 # Render the current Scene, this should be a READ-ONLY property
638 self._SCENE = Scene.GetCurrent()
640 # Use the aspect ratio of the scene rendering context
641 context = self._SCENE.getRenderingContext()
643 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
644 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
645 float(context.aspectRatioY())
648 # Render from the currently active camera
649 self.cameraObj = self._SCENE.getCurrentCamera()
651 # Get a projector for this camera.
652 # NOTE: the projector wants object in world coordinates,
653 # so we should remember to apply modelview transformations
654 # _before_ we do projection transformations.
655 self.proj = Projector(self.cameraObj, self.canvasRatio)
657 # Get the list of lighting sources
658 obj_lst = self._SCENE.getChildren()
659 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
661 # When there are no lights we use a default lighting source
662 # that have the same position of the camera
663 if len(self.lights) == 0:
665 lobj = Object.New('Lamp')
666 lobj.loc = self.cameraObj.loc
668 self.lights.append(lobj)
675 def doRendering(self, outputWriter, animation=False):
676 """Render picture or animation and write it out.
679 - a Vector writer object that will be used to output the result.
680 - a flag to tell if we want to render an animation or only the
684 context = self._SCENE.getRenderingContext()
685 currentFrame = context.currentFrame()
687 # Handle the animation case
689 startFrame = currentFrame
690 endFrame = startFrame
693 startFrame = context.startFrame()
694 endFrame = context.endFrame()
695 outputWriter.open(startFrame, endFrame)
697 # Do the rendering process frame by frame
698 print "Start Rendering!"
699 for f in range(startFrame, endFrame+1):
700 context.currentFrame(f)
702 # Use some temporary workspace, a full copy of the scene
703 inputScene = self._SCENE.copy(2)
706 renderedScene = self.doRenderScene(inputScene)
708 self._SCENE.makeCurrent()
709 Scene.unlink(inputScene)
712 outputWriter.printCanvas(renderedScene,
713 doPrintPolygons = PRINT_POLYGONS,
714 doPrintEdges = PRINT_EDGES,
715 showHiddenEdges = SHOW_HIDDEN_EDGES)
717 # clear the rendered scene
718 self._SCENE.makeCurrent()
719 Scene.unlink(renderedScene)
724 context.currentFrame(currentFrame)
727 def doRenderScene(self, workScene):
728 """Control the rendering process.
730 Here we control the entire rendering process invoking the operation
731 needed to transform and project the 3D scene in two dimensions.
734 # global processing of the scene
736 self._doConvertGeometricObjToMesh(workScene)
738 self._doSceneClipping(workScene)
741 # XXX: Joining objects does not work in batch mode!!
742 # Do not touch the following if, please :)
744 global OPTIMIZE_FOR_SPACE
745 if Blender.mode == 'background':
746 print "\nWARNING! Joining objects not supported in background mode!\n"
747 OPTIMIZE_FOR_SPACE = False
749 if OPTIMIZE_FOR_SPACE:
750 self._joinMeshObjectsInScene(workScene)
753 self._doSceneDepthSorting(workScene)
755 # Per object activities
757 Objects = workScene.getChildren()
760 if obj.getType() != 'Mesh':
761 print "Only Mesh supported! - Skipping type:", obj.getType()
764 print "Rendering: ", obj.getName()
768 self._doModelToWorldCoordinates(mesh, obj.matrix)
770 self._doObjectDepthSorting(mesh)
772 # We use both Mesh and NMesh because for depth sorting we change
773 # face order and Mesh class don't let us to do that.
775 mesh = obj.getData(mesh=1)
777 self._doBackFaceCulling(mesh)
779 self._doColorAndLighting(mesh)
781 self._doEdgesStyle(mesh, edgeSelectionStyles[EDGE_STYLE])
783 self._doProjection(mesh, self.proj)
785 # Update the object data, important! :)
797 def _getObjPosition(self, obj):
798 """Return the obj position in World coordinates.
800 return obj.matrix.translationPart()
802 def _cameraViewDirection(self):
803 """Get the View Direction form the camera matrix.
805 return Vector(self.cameraObj.matrix[2]).resize3D()
810 def _isFaceVisible(self, face):
811 """Determine if a face of an object is visible from the current camera.
813 The view vector is calculated from the camera location and one of the
814 vertices of the face (expressed in World coordinates, after applying
815 modelview transformations).
817 After those transformations we determine if a face is visible by
818 computing the angle between the face normal and the view vector, this
819 angle has to be between -90 and 90 degrees for the face to be visible.
820 This corresponds somehow to the dot product between the two, if it
821 results > 0 then the face is visible.
823 There is no need to normalize those vectors since we are only interested in
824 the sign of the cross product and not in the product value.
826 NOTE: here we assume the face vertices are in WorldCoordinates, so
827 please transform the object _before_ doing the test.
830 normal = Vector(face.no)
831 camPos = self._getObjPosition(self.cameraObj)
834 # View Vector in orthographics projections is the view Direction of
836 if self.cameraObj.data.getType() == 1:
837 view_vect = self._cameraViewDirection()
839 # View vector in perspective projections can be considered as
840 # the difference between the camera position and one point of
841 # the face, we choose the farthest point from the camera.
842 if self.cameraObj.data.getType() == 0:
843 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
846 # if d > 0 the face is visible from the camera
847 d = view_vect * normal
857 def _doConvertGeometricObjToMesh(self, scene):
858 """Convert all "geometric" objects to mesh ones.
860 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
862 Objects = scene.getChildren()
863 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
866 obj = self._convertToRawMeshObj(obj)
868 scene.unlink(old_obj)
871 # XXX Workaround for Text and Curve which have some normals
872 # inverted when they are converted to Mesh, REMOVE that when
873 # blender will fix that!!
874 if old_obj.getType() in ['Curve', 'Text']:
875 me = obj.getData(mesh=1)
876 for f in me.faces: f.sel = 1;
877 for v in me.verts: v.sel = 1;
884 def _doSceneClipping(self, scene):
885 """Clip objects against the View Frustum.
887 For now clip away only objects according to their center position.
890 cpos = self._getObjPosition(self.cameraObj)
891 view_vect = self._cameraViewDirection()
893 near = self.cameraObj.data.clipStart
894 far = self.cameraObj.data.clipEnd
896 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
897 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
898 fovy = fovy * 360.0/pi
900 Objects = scene.getChildren()
902 if o.getType() != 'Mesh': continue;
904 obj_vect = Vector(cpos) - self._getObjPosition(o)
906 d = obj_vect*view_vect
907 theta = AngleBetweenVecs(obj_vect, view_vect)
909 # if the object is outside the view frustum, clip it away
910 if (d < near) or (d > far) or (theta > fovy):
913 def _doSceneDepthSorting(self, scene):
914 """Sort objects in the scene.
916 The object sorting is done accordingly to the object centers.
919 c = self._getObjPosition(self.cameraObj)
921 by_center_pos = (lambda o1, o2:
922 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
923 cmp((self._getObjPosition(o1) - Vector(c)).length,
924 (self._getObjPosition(o2) - Vector(c)).length)
927 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
928 # then ob1 goes farther than obj2, useful when obj2 has holes
931 Objects = scene.getChildren()
932 Objects.sort(by_center_pos)
939 def _joinMeshObjectsInScene(self, scene):
940 """Merge all the Mesh Objects in a scene into a single Mesh Object.
943 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
945 # FIXME: Object.join() do not work if the list contains 1 object
950 bigObj = Object.New('Mesh', 'BigOne')
963 def _convertToRawMeshObj(self, object):
964 """Convert geometry based object to a mesh object.
966 me = Mesh.New('RawMesh_'+object.name)
967 me.getFromObject(object.name)
969 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
972 # If the object has no materials set a default material
974 me.materials = [Material.New()]
975 #for f in me.faces: f.mat = 0
977 newObject.setMatrix(object.getMatrix())
981 def _doModelToWorldCoordinates(self, mesh, matrix):
982 """Transform object coordinates to world coordinates.
984 This step is done simply applying to the object its tranformation
985 matrix and recalculating its normals.
987 # XXX FIXME: blender do not transform normals in the right way when
988 # there are negative scale values
989 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
990 print "WARNING: Negative scales, expect incorrect results!"
992 mesh.transform(matrix, True)
994 def _doObjectDepthSorting(self, mesh):
995 """Sort faces in an object.
997 The faces in the object are sorted following the distance of the
998 vertices from the camera position.
1000 c = self._getObjPosition(self.cameraObj)
1002 # hackish sorting of faces
1004 # Sort faces according to the max distance from the camera
1005 by_max_vert_dist = (lambda f1, f2:
1006 cmp(max([(Vector(v.co)-Vector(c)).length for v in f1]),
1007 max([(Vector(v.co)-Vector(c)).length for v in f2])))
1009 # Sort faces according to the min distance from the camera
1010 by_min_vert_dist = (lambda f1, f2:
1011 cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
1012 min([(Vector(v.co)-Vector(c)).length for v in f2])))
1014 # Sort faces according to the avg distance from the camera
1015 by_avg_vert_dist = (lambda f1, f2:
1016 cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
1017 sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
1019 mesh.faces.sort(by_max_vert_dist)
1020 mesh.faces.reverse()
1022 def _doBackFaceCulling(self, mesh):
1023 """Simple Backface Culling routine.
1025 At this level we simply do a visibility test face by face and then
1026 select the vertices belonging to visible faces.
1029 # Select all vertices, so edges can be displayed even if there are no
1031 for v in mesh.verts:
1034 Mesh.Mode(Mesh.SelectModes['FACE'])
1036 for f in mesh.faces:
1038 if self._isFaceVisible(f):
1041 # Is this the correct way to propagate the face selection info to the
1042 # vertices belonging to a face ??
1043 # TODO: Using the Mesh module this should come for free. Right?
1044 #Mesh.Mode(Mesh.SelectModes['VERTEX'])
1045 #for f in mesh.faces:
1047 # for v in f: v.sel = 0;
1049 #for f in mesh.faces:
1051 # for v in f: v.sel = 1;
1053 def _doColorAndLighting(self, mesh):
1054 """Apply an Illumination model to the object.
1056 The Illumination model used is the Phong one, it may be inefficient,
1057 but I'm just learning about rendering and starting from Phong seemed
1058 the most natural way.
1061 # If the mesh has vertex colors already, use them,
1062 # otherwise turn them on and do some calculations
1063 if mesh.vertexColors:
1065 mesh.vertexColors = 1
1067 materials = mesh.materials
1069 # TODO: use multiple lighting sources
1070 light_obj = self.lights[0]
1071 light_pos = self._getObjPosition(light_obj)
1072 light = light_obj.data
1074 camPos = self._getObjPosition(self.cameraObj)
1076 # We do per-face color calculation (FLAT Shading), we can easily turn
1077 # to a per-vertex calculation if we want to implement some shading
1078 # technique. For an example see:
1079 # http://www.miralab.unige.ch/papers/368.pdf
1080 for f in mesh.faces:
1086 mat = materials[f.mat]
1088 # A new default material
1090 mat = Material.New('defMat')
1092 L = Vector(light_pos).normalize()
1094 V = (Vector(camPos) - Vector(f.v[0].co)).normalize()
1096 N = Vector(f.no).normalize()
1098 R = 2 * (N*L) * N - L
1100 # TODO: Attenuation factor (not used for now)
1101 a0 = 1; a1 = 0.0; a2 = 0.0
1102 d = (Vector(f.v[0].co) - Vector(light_pos)).length
1103 fd = min(1, 1.0/(a0 + a1*d + a2*d*d))
1107 ka = mat.getAmb() * Vector([0.1, 0.1, 0.1])
1110 # Diffuse component (add light.col for kd)
1111 kd = mat.getRef() * Vector(mat.getRGBCol())
1112 Ip = light.getEnergy()
1113 Idiff = Ip * kd * (N*L)
1115 # Specular component
1116 ks = mat.getSpec() * Vector(mat.getSpecCol())
1117 ns = mat.getHardness()
1118 Ispec = Ip * ks * pow((V * R), ns)
1120 # Emissive component
1121 ki = Vector([mat.getEmit()]*3)
1123 I = ki + Iamb + Idiff + Ispec
1125 # Set Alpha component
1127 I.append(mat.getAlpha())
1129 # Clamp I values between 0 and 1
1130 I = [ min(c, 1) for c in I]
1131 I = [ max(0, c) for c in I]
1132 tmp_col = [ int(c * 255.0) for c in I]
1140 def _doEdgesStyle(self, mesh, edgestyleSelect):
1141 """Process Mesh Edges accroding to a given selection style.
1143 Examples of algorithms:
1146 given an edge if its adjacent faces have the same normal (that is
1147 they are complanar), than deselect it.
1150 given an edge if one its adjacent faces is frontfacing and the
1151 other is backfacing, than select it, else deselect.
1154 Mesh.Mode(Mesh.SelectModes['EDGE'])
1156 for e in mesh.edges:
1159 if edgestyleSelect(e, mesh):
1162 def _doProjection(self, mesh, projector):
1163 """Calculate the Projection for the object.
1165 # TODO: maybe using the object.transform() can be faster?
1167 for v in mesh.verts:
1168 p = projector.doProjection(v.co)
1175 # ---------------------------------------------------------------------
1177 ## GUI Class and Main Program
1179 # ---------------------------------------------------------------------
1182 from Blender import BGL, Draw
1183 from Blender.BGL import *
1189 # Output Format menu
1190 default_value = outputWriters.keys().index(OUTPUT_FORMAT)+1
1191 GUI.outFormatMenu = Draw.Create(default_value)
1192 GUI.evtOutFormatMenu = 0
1194 # Animation toggle button
1195 GUI.animToggle = Draw.Create(RENDER_ANIMATION)
1196 GUI.evtAnimToggle = 1
1198 # Join Objects toggle button
1199 GUI.joinObjsToggle = Draw.Create(OPTIMIZE_FOR_SPACE)
1200 GUI.evtJoinObjsToggle = 2
1202 # Render filled polygons
1203 GUI.polygonsToggle = Draw.Create(PRINT_POLYGONS)
1204 GUI.evtPolygonsToggle = 3
1205 # We hide the POLYGON_EXPANSION_TRICK, for now
1207 # Render polygon edges
1208 GUI.showEdgesToggle = Draw.Create(PRINT_EDGES)
1209 GUI.evtShowEdgesToggle = 4
1211 # Render hidden edges
1212 GUI.showHiddenEdgesToggle = Draw.Create(SHOW_HIDDEN_EDGES)
1213 GUI.evtShowHiddenEdgesToggle = 5
1216 default_value = edgeSelectionStyles.keys().index(EDGE_STYLE)+1
1217 GUI.edgeStyleMenu = Draw.Create(default_value)
1218 GUI.evtEdgeStyleMenu = 6
1221 GUI.edgeWidthSlider = Draw.Create(EDGES_WIDTH)
1222 GUI.evtEdgeWidthSlider = 7
1225 GUI.evtRenderButton = 8
1228 GUI.evtExitButton = 9
1232 # initialize static members
1235 glClear(GL_COLOR_BUFFER_BIT)
1236 glColor3f(0.0, 0.0, 0.0)
1237 glRasterPos2i(10, 350)
1238 Draw.Text("VRM: Vector Rendering Method script.")
1239 glRasterPos2i(10, 335)
1240 Draw.Text("Press Q or ESC to quit.")
1242 # Build the output format menu
1243 glRasterPos2i(10, 310)
1244 Draw.Text("Select the output Format:")
1245 outMenuStruct = "Output Format %t"
1246 for t in outputWriters.keys():
1247 outMenuStruct = outMenuStruct + "|%s" % t
1248 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
1249 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
1252 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
1253 10, 260, 160, 18, GUI.animToggle.val,
1254 "Toggle rendering of animations")
1256 # Join Objects toggle
1257 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
1258 10, 235, 160, 18, GUI.joinObjsToggle.val,
1259 "Join objects in the rendered file")
1262 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
1264 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
1267 glRasterPos2i(200, 310)
1268 Draw.Text("Rendering Style:")
1271 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
1272 200, 285, 160, 18, GUI.polygonsToggle.val,
1273 "Render filled polygons")
1276 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
1277 200, 260, 160, 18, GUI.showEdgesToggle.val,
1278 "Render polygon edges")
1280 if GUI.showEdgesToggle.val == 1:
1283 edgeStyleMenuStruct = "Edge Style %t"
1284 for t in edgeSelectionStyles.keys():
1285 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t
1286 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
1287 200, 235, 160, 18, GUI.edgeStyleMenu.val,
1288 "Choose the edge style")
1291 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
1292 200, 210, 160, 18, GUI.edgeWidthSlider.val,
1293 0.0, 10.0, 0, "Change Edge Width")
1296 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
1297 GUI.evtShowHiddenEdgesToggle,
1298 200, 185, 160, 18, GUI.showHiddenEdgesToggle.val,
1299 "Render hidden edges as dashed lines")
1301 glRasterPos2i(10, 160)
1302 Draw.Text("Antonio Ospite (c) 2006")
1304 def event(evt, val):
1306 if evt == Draw.ESCKEY or evt == Draw.QKEY:
1313 def button_event(evt):
1314 global PRINT_POLYGONS
1315 global POLYGON_EXPANSION_TRICK
1317 global SHOW_HIDDEN_EDGES
1320 global RENDER_ANIMATION
1321 global OPTIMIZE_FOR_SPACE
1322 global OUTPUT_FORMAT
1324 if evt == GUI.evtExitButton:
1326 elif evt == GUI.evtOutFormatMenu:
1327 i = GUI.outFormatMenu.val - 1
1328 OUTPUT_FORMAT = outputWriters.keys()[i]
1329 elif evt == GUI.evtAnimToggle:
1330 RENDER_ANIMATION = bool(GUI.animToggle.val)
1331 elif evt == GUI.evtJoinObjsToggle:
1332 OPTIMIZE_FOR_SPACE = bool(GUI.joinObjsToggle.val)
1333 elif evt == GUI.evtPolygonsToggle:
1334 PRINT_POLYGONS = bool(GUI.polygonsToggle.val)
1335 elif evt == GUI.evtShowEdgesToggle:
1336 PRINT_EDGES = bool(GUI.showEdgesToggle.val)
1337 elif evt == GUI.evtShowHiddenEdgesToggle:
1338 SHOW_HIDDEN_EDGES = bool(GUI.showHiddenEdgesToggle.val)
1339 elif evt == GUI.evtEdgeStyleMenu:
1340 i = GUI.edgeStyleMenu.val - 1
1341 EDGE_STYLE = edgeSelectionStyles.keys()[i]
1342 elif evt == GUI.evtEdgeWidthSlider:
1343 EDGES_WIDTH = float(GUI.edgeWidthSlider.val)
1344 elif evt == GUI.evtRenderButton:
1345 label = "Save %s" % OUTPUT_FORMAT
1346 # Show the File Selector
1348 Blender.Window.FileSelector(vectorize, label, outputfile)
1351 print "Event: %d not handled!" % evt
1359 print "PRINT_POLYGONS:", PRINT_POLYGONS
1360 print "POLYGON_EXPANSION_TRICK:", POLYGON_EXPANSION_TRICK
1361 print "PRINT_EDGES:", PRINT_EDGES
1362 print "SHOW_HIDDEN_EDGES:", SHOW_HIDDEN_EDGES
1363 print "EDGE_STYLE:", EDGE_STYLE
1364 print "EDGES_WIDTH:", EDGES_WIDTH
1365 print "RENDER_ANIMATION:", RENDER_ANIMATION
1366 print "OPTIMIZE_FOR_SPACE:", OPTIMIZE_FOR_SPACE
1367 print "OUTPUT_FORMAT:", OUTPUT_FORMAT
1369 _init = staticmethod(_init)
1370 draw = staticmethod(draw)
1371 event = staticmethod(event)
1372 button_event = staticmethod(button_event)
1373 conf_debug = staticmethod(conf_debug)
1375 # A wrapper function for the vectorizing process
1376 def vectorize(filename):
1377 """The vectorizing process is as follows:
1379 - Instanciate the writer and the renderer
1384 print "\nERROR: invalid file name!"
1387 from Blender import Window
1388 editmode = Window.EditMode()
1389 if editmode: Window.EditMode(0)
1391 actualWriter = outputWriters[OUTPUT_FORMAT]
1392 writer = actualWriter(filename)
1394 renderer = Renderer()
1395 renderer.doRendering(writer, RENDER_ANIMATION)
1397 if editmode: Window.EditMode(1)
1401 if __name__ == "__main__":
1404 basename = Blender.sys.basename(Blender.Get('filename'))
1406 outputfile = Blender.sys.splitext(basename)[0] + "." + str(OUTPUT_FORMAT).lower()
1408 if Blender.mode == 'background':
1409 vectorize(outputfile)
1411 Draw.Register(GUI.draw, GUI.event, GUI.button_event)