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
81 POLYGON_EXPANSION_TRICK = True
84 SHOW_HIDDEN_EDGES = False
85 #EDGE_STYLE = 'normal'
86 EDGE_STYLE = 'silhouette'
89 RENDER_ANIMATION = False
91 OPTIMIZE_FOR_SPACE = True
96 # ---------------------------------------------------------------------
100 # ---------------------------------------------------------------------
105 def getEdgeAdjacentFaces(self, 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(self, 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 = self.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(self, 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 = self.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)
158 # ---------------------------------------------------------------------
160 ## Projections classes
162 # ---------------------------------------------------------------------
165 """Calculate the projection of an object given the camera.
167 A projector is useful to so some per-object transformation to obtain the
168 projection of an object given the camera.
170 The main method is #doProjection# see the method description for the
174 def __init__(self, cameraObj, canvasRatio):
175 """Calculate the projection matrix.
177 The projection matrix depends, in this case, on the camera settings.
178 TAKE CARE: This projector expects vertices in World Coordinates!
181 camera = cameraObj.getData()
183 aspect = float(canvasRatio[0])/float(canvasRatio[1])
184 near = camera.clipStart
187 scale = float(camera.scale)
189 fovy = atan(0.5/aspect/(camera.lens/32))
190 fovy = fovy * 360.0/pi
192 # What projection do we want?
194 #mP = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale)
195 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
197 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
199 # View transformation
200 cam = Matrix(cameraObj.getInverseMatrix())
205 self.projectionMatrix = mP
211 def doProjection(self, v):
212 """Project the point on the view plane.
214 Given a vertex calculate the projection using the current projection
218 # Note that we have to work on the vertex using homogeneous coordinates
219 p = self.projectionMatrix * Vector(v).resize4D()
235 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
236 """Return a perspective projection matrix.
239 top = near * tan(fovy * pi / 360.0)
243 x = (2.0 * near) / (right-left)
244 y = (2.0 * near) / (top-bottom)
245 a = (right+left) / (right-left)
246 b = (top+bottom) / (top - bottom)
247 c = - ((far+near) / (far-near))
248 d = - ((2*far*near)/(far-near))
254 [0.0, 0.0, -1.0, 0.0])
258 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
259 """Return an orthogonal projection matrix.
262 # The 11 in the formula was found emiprically
263 top = near * tan(fovy * pi / 360.0) * (scale * 11)
265 left = bottom * aspect
270 tx = -((right+left)/rl)
271 ty = -((top+bottom)/tb)
275 [2.0/rl, 0.0, 0.0, tx],
276 [0.0, 2.0/tb, 0.0, ty],
277 [0.0, 0.0, 2.0/fn, tz],
278 [0.0, 0.0, 0.0, 1.0])
284 # ---------------------------------------------------------------------
286 ## 2D Object representation class
288 # ---------------------------------------------------------------------
290 # TODO: a class to represent the needed properties of a 2D vector image
291 # For now just using a [N]Mesh structure.
294 # ---------------------------------------------------------------------
296 ## Vector Drawing Classes
298 # ---------------------------------------------------------------------
304 A class for printing output in a vectorial format.
306 Given a 2D representation of the 3D scene the class is responsible to
307 write it is a vector format.
309 Every subclasses of VectorWriter must have at last the following public
313 - printCanvas(self, scene,
314 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
317 def __init__(self, fileName):
318 """Set the output file name and other properties"""
320 self.outputFileName = fileName
323 context = Scene.GetCurrent().getRenderingContext()
324 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
328 self.animation = False
335 def open(self, startFrame=1, endFrame=1):
336 if startFrame != endFrame:
337 self.startFrame = startFrame
338 self.endFrame = endFrame
339 self.animation = True
341 self.file = open(self.outputFileName, "w")
342 print "Outputting to: ", self.outputFileName
350 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
351 showHiddenEdges=False):
352 """This is the interface for the needed printing routine.
359 class SVGVectorWriter(VectorWriter):
360 """A concrete class for writing SVG output.
363 def __init__(self, fileName):
364 """Simply call the parent Contructor.
366 VectorWriter.__init__(self, fileName)
373 def open(self, startFrame=1, endFrame=1):
374 """Do some initialization operations.
376 VectorWriter.open(self, startFrame, endFrame)
380 """Do some finalization operation.
384 # remember to call the close method of the parent
385 VectorWriter.close(self)
388 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
389 showHiddenEdges=False):
390 """Convert the scene representation to SVG.
393 Objects = scene.getChildren()
395 context = scene.getRenderingContext()
396 framenumber = context.currentFrame()
399 framestyle = "display:none"
401 framestyle = "display:block"
403 # Assign an id to this group so we can set properties on it using DOM
404 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
405 (framenumber, framestyle) )
409 if(obj.getType() != 'Mesh'):
412 self.file.write("<g id=\"%s\">\n" % obj.getName())
414 mesh = obj.getData(mesh=1)
417 self._printPolygons(mesh)
420 self._printEdges(mesh, showHiddenEdges)
422 self.file.write("</g>\n")
424 self.file.write("</g>\n")
431 def _calcCanvasCoord(self, v):
432 """Convert vertex in scene coordinates to canvas coordinates.
435 pt = Vector([0, 0, 0])
437 mW = float(self.canvasSize[0])/2.0
438 mH = float(self.canvasSize[1])/2.0
440 # rescale to canvas size
441 pt[0] = v.co[0]*mW + mW
442 pt[1] = v.co[1]*mH + mH
445 # For now we want (0,0) in the top-left corner of the canvas.
446 # Mirror and translate along y
448 pt[1] += self.canvasSize[1]
452 def _printHeader(self):
453 """Print SVG header."""
455 self.file.write("<?xml version=\"1.0\"?>\n")
456 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n")
457 self.file.write("\t\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
458 self.file.write("<svg version=\"1.1\"\n")
459 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
460 self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
465 self.file.write("""\n<script><![CDATA[
469 /* FIXME: Use 1000 as interval as lower values gives problems */
470 timerID = setInterval("NextFrame()", 1000);
471 globalFrameCounter=%d;
475 currentElement = document.getElementById('frame'+globalFrameCounter)
476 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
483 if (globalFrameCounter > globalEndFrame)
485 clearInterval(timerID)
491 previousElement.style.display="none";
493 currentElement.style.display="block";
494 globalFrameCounter++;
498 \n""" % (self.startFrame, self.endFrame, self.startFrame) )
500 def _printFooter(self):
501 """Print the SVG footer."""
503 self.file.write("\n</svg>\n")
505 def _printPolygons(self, mesh):
506 """Print the selected (visible) polygons.
509 if len(mesh.faces) == 0:
512 self.file.write("<g>\n")
514 for face in mesh.faces:
518 self.file.write("<polygon points=\"")
521 p = self._calcCanvasCoord(v)
522 self.file.write("%g,%g " % (p[0], p[1]))
524 # get rid of the last blank space, just cosmetics here.
525 self.file.seek(-1, 1)
526 self.file.write("\"\n")
528 # take as face color the first vertex color
529 # TODO: the average of vetrex colors?
532 color = [fcol.r, fcol.g, fcol.b, fcol.a]
534 color = [255, 255, 255, 255]
536 # use the stroke property to alleviate the "adjacent edges" problem,
537 # we simulate polygon expansion using borders,
538 # see http://www.antigrain.com/svg/index.html for more info
542 # Convert the color to the #RRGGBB form
543 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
545 # Handle transparent polygons
548 opacity = float(color[3])/255.0
549 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
551 self.file.write("\tstyle=\"fill:" + str_col + ";")
552 self.file.write(opacity_string)
553 if POLYGON_EXPANSION_TRICK:
554 self.file.write(" stroke:" + str_col + ";")
555 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
556 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
557 self.file.write("\"/>\n")
559 self.file.write("</g>\n")
561 def _printEdges(self, mesh, showHiddenEdges=False):
562 """Print the wireframe using mesh edges.
565 stroke_width=EDGES_WIDTH
566 stroke_col = [0, 0, 0]
568 self.file.write("<g>\n")
572 hidden_stroke_style = ""
574 # We consider an edge visible if _both_ its vertices are selected,
575 # hence an edge is hidden if _any_ of its vertices is deselected.
577 if showHiddenEdges == False:
580 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
582 p1 = self._calcCanvasCoord(e.v1)
583 p2 = self._calcCanvasCoord(e.v2)
585 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
586 % ( p1[0], p1[1], p2[0], p2[1] ) )
587 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
588 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
589 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
590 self.file.write(hidden_stroke_style)
591 self.file.write("\"/>\n")
593 self.file.write("</g>\n")
597 # ---------------------------------------------------------------------
601 # ---------------------------------------------------------------------
604 """Render a scene viewed from a given camera.
606 This class is responsible of the rendering process, transformation and
607 projection of the objects in the scene are invoked by the renderer.
609 The rendering is done using the active camera for the current scene.
613 """Make the rendering process only for the current scene by default.
615 We will work on a copy of the scene, be sure that the current scene do
616 not get modified in any way.
619 # Render the current Scene, this should be a READ-ONLY property
620 self._SCENE = Scene.GetCurrent()
622 # Use the aspect ratio of the scene rendering context
623 context = self._SCENE.getRenderingContext()
625 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
626 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
627 float(context.aspectRatioY())
630 # Render from the currently active camera
631 self.cameraObj = self._SCENE.getCurrentCamera()
633 # Get the list of lighting sources
634 obj_lst = self._SCENE.getChildren()
635 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
637 if len(self.lights) == 0:
639 lobj = Object.New('Lamp')
641 self.lights.append(lobj)
648 def doRendering(self, outputWriter, animation=False):
649 """Render picture or animation and write it out.
652 - a Vector writer object that will be used to output the result.
653 - a flag to tell if we want to render an animation or only the
657 context = self._SCENE.getRenderingContext()
658 currentFrame = context.currentFrame()
660 # Handle the animation case
662 startFrame = currentFrame
663 endFrame = startFrame
666 startFrame = context.startFrame()
667 endFrame = context.endFrame()
668 outputWriter.open(startFrame, endFrame)
670 # Do the rendering process frame by frame
671 print "Start Rendering!"
672 for f in range(startFrame, endFrame+1):
673 context.currentFrame(f)
675 renderedScene = self.doRenderScene(self._SCENE)
676 outputWriter.printCanvas(renderedScene,
677 doPrintPolygons = PRINT_POLYGONS,
678 doPrintEdges = PRINT_EDGES,
679 showHiddenEdges = SHOW_HIDDEN_EDGES)
681 # clear the rendered scene
682 self._SCENE.makeCurrent()
683 Scene.unlink(renderedScene)
688 context.currentFrame(currentFrame)
691 def doRenderScene(self, inputScene):
692 """Control the rendering process.
694 Here we control the entire rendering process invoking the operation
695 needed to transform and project the 3D scene in two dimensions.
698 # Use some temporary workspace, a full copy of the scene
699 workScene = inputScene.copy(2)
701 # Get a projector for this scene.
702 # NOTE: the projector wants object in world coordinates,
703 # so we should apply modelview transformations _before_
704 # projection transformations
705 proj = Projector(self.cameraObj, self.canvasRatio)
707 # global processing of the scene
709 self._doConvertGeometricObjToMesh(workScene)
711 self._doSceneClipping(workScene)
714 # XXX: Joining objects does not work in batch mode!!
715 # Do not touch the following if, please :)
717 global OPTIMIZE_FOR_SPACE
718 if Blender.mode == 'background':
719 print "\nWARNING! Joining objects not supported in background mode!\n"
720 OPTIMIZE_FOR_SPACE = False
722 if OPTIMIZE_FOR_SPACE:
723 self._joinMeshObjectsInScene(workScene)
726 self._doSceneDepthSorting(workScene)
728 # Per object activities
730 Objects = workScene.getChildren()
733 if obj.getType() != 'Mesh':
734 print "Only Mesh supported! - Skipping type:", obj.getType()
737 print "Rendering: ", obj.getName()
741 self._doModelToWorldCoordinates(mesh, obj.matrix)
743 self._doObjectDepthSorting(mesh)
745 # We use both Mesh and NMesh because for depth sorting we change
746 # face order and Mesh class don't let us to do that.
748 mesh = obj.getData(mesh=1)
750 self._doBackFaceCulling(mesh)
752 self._doColorAndLighting(mesh)
754 self._doEdgesStyle(mesh, edgeSelectionStyles[EDGE_STYLE])
756 self._doProjection(mesh, proj)
758 # Update the object data, important! :)
770 def _getObjPosition(self, obj):
771 """Return the obj position in World coordinates.
773 return obj.matrix.translationPart()
775 def _cameraViewDirection(self):
776 """Get the View Direction form the camera matrix.
778 return Vector(self.cameraObj.matrix[2]).resize3D()
783 def _isFaceVisible(self, face):
784 """Determine if a face of an object is visible from the current camera.
786 The view vector is calculated from the camera location and one of the
787 vertices of the face (expressed in World coordinates, after applying
788 modelview transformations).
790 After those transformations we determine if a face is visible by
791 computing the angle between the face normal and the view vector, this
792 angle has to be between -90 and 90 degrees for the face to be visible.
793 This corresponds somehow to the dot product between the two, if it
794 results > 0 then the face is visible.
796 There is no need to normalize those vectors since we are only interested in
797 the sign of the cross product and not in the product value.
799 NOTE: here we assume the face vertices are in WorldCoordinates, so
800 please transform the object _before_ doing the test.
803 normal = Vector(face.no)
804 camPos = self._getObjPosition(self.cameraObj)
807 # View Vector in orthographics projections is the view Direction of
809 if self.cameraObj.data.getType() == 1:
810 view_vect = self._cameraViewDirection()
812 # View vector in perspective projections can be considered as
813 # the difference between the camera position and one point of
814 # the face, we choose the farthest point from the camera.
815 if self.cameraObj.data.getType() == 0:
816 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
819 # if d > 0 the face is visible from the camera
820 d = view_vect * normal
830 def _doConvertGeometricObjToMesh(self, scene):
831 """Convert all "geometric" objects to mesh ones.
833 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
835 Objects = scene.getChildren()
836 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
839 obj = self._convertToRawMeshObj(obj)
841 scene.unlink(old_obj)
844 # XXX Workaround for Text and Curve which have some normals
845 # inverted when they are converted to Mesh, REMOVE that when
846 # blender will fix that!!
847 if old_obj.getType() in ['Curve', 'Text']:
848 me = obj.getData(mesh=1)
849 for f in me.faces: f.sel = 1;
850 for v in me.verts: v.sel = 1;
857 def _doSceneClipping(self, scene):
858 """Clip objects against the View Frustum.
860 For now clip away only objects according to their center position.
863 cpos = self._getObjPosition(self.cameraObj)
864 view_vect = self._cameraViewDirection()
866 near = self.cameraObj.data.clipStart
867 far = self.cameraObj.data.clipEnd
869 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
870 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
871 fovy = fovy * 360.0/pi
873 Objects = scene.getChildren()
875 if o.getType() != 'Mesh': continue;
877 obj_vect = Vector(cpos) - self._getObjPosition(o)
879 d = obj_vect*view_vect
880 theta = AngleBetweenVecs(obj_vect, view_vect)
882 # if the object is outside the view frustum, clip it away
883 if (d < near) or (d > far) or (theta > fovy):
886 def _doSceneDepthSorting(self, scene):
887 """Sort objects in the scene.
889 The object sorting is done accordingly to the object centers.
892 c = self._getObjPosition(self.cameraObj)
894 by_center_pos = (lambda o1, o2:
895 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
896 cmp((self._getObjPosition(o1) - Vector(c)).length,
897 (self._getObjPosition(o2) - Vector(c)).length)
900 # TODO: implement sorting by bounding box, if obj1.bb is inside obj2.bb,
901 # then ob1 goes farther than obj2, useful when obj2 has holes
904 Objects = scene.getChildren()
905 Objects.sort(by_center_pos)
912 def _joinMeshObjectsInScene(self, scene):
913 """Merge all the Mesh Objects in a scene into a single Mesh Object.
916 bigObj = Object.New('Mesh', 'BigOne')
919 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
930 def _convertToRawMeshObj(self, object):
931 """Convert geometry based object to a mesh object.
933 me = Mesh.New('RawMesh_'+object.name)
934 me.getFromObject(object.name)
936 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
939 # If the object has no materials set a default material
941 me.materials = [Material.New()]
942 #for f in me.faces: f.mat = 0
944 newObject.setMatrix(object.getMatrix())
948 def _doModelToWorldCoordinates(self, mesh, matrix):
949 """Transform object coordinates to world coordinates.
951 This step is done simply applying to the object its tranformation
952 matrix and recalculating its normals.
954 mesh.transform(matrix, True)
956 def _doObjectDepthSorting(self, mesh):
957 """Sort faces in an object.
959 The faces in the object are sorted following the distance of the
960 vertices from the camera position.
962 c = self._getObjPosition(self.cameraObj)
964 # hackish sorting of faces
966 # Sort faces according to the max distance from the camera
967 by_max_vert_dist = (lambda f1, f2:
968 cmp(max([(Vector(v.co)-Vector(c)).length for v in f1]),
969 max([(Vector(v.co)-Vector(c)).length for v in f2])))
971 # Sort faces according to the min distance from the camera
972 by_min_vert_dist = (lambda f1, f2:
973 cmp(min([(Vector(v.co)-Vector(c)).length for v in f1]),
974 min([(Vector(v.co)-Vector(c)).length for v in f2])))
976 # Sort faces according to the avg distance from the camera
977 by_avg_vert_dist = (lambda f1, f2:
978 cmp(sum([(Vector(v.co)-Vector(c)).length for v in f1])/len(f1),
979 sum([(Vector(v.co)-Vector(c)).length for v in f2])/len(f2)))
981 mesh.faces.sort(by_max_vert_dist)
984 def _doBackFaceCulling(self, mesh):
985 """Simple Backface Culling routine.
987 At this level we simply do a visibility test face by face and then
988 select the vertices belonging to visible faces.
991 # Select all vertices, so edges can be displayed even if there are no
996 Mesh.Mode(Mesh.SelectModes['FACE'])
1000 if self._isFaceVisible(f):
1003 # Is this the correct way to propagate the face selection info to the
1004 # vertices belonging to a face ??
1005 # TODO: Using the Mesh module this should come for free. Right?
1006 #Mesh.Mode(Mesh.SelectModes['VERTEX'])
1007 #for f in mesh.faces:
1009 # for v in f: v.sel = 0;
1011 #for f in mesh.faces:
1013 # for v in f: v.sel = 1;
1015 def _doColorAndLighting(self, mesh):
1016 """Apply an Illumination model to the object.
1018 The Illumination model used is the Phong one, it may be inefficient,
1019 but I'm just learning about rendering and starting from Phong seemed
1020 the most natural way.
1023 # If the mesh has vertex colors already, use them,
1024 # otherwise turn them on and do some calculations
1025 if mesh.vertexColors:
1027 mesh.vertexColors = 1
1029 materials = mesh.materials
1031 # TODO: use multiple lighting sources
1032 light_obj = self.lights[0]
1033 light_pos = self._getObjPosition(light_obj)
1034 light = light_obj.data
1036 camPos = self._getObjPosition(self.cameraObj)
1038 # We do per-face color calculation (FLAT Shading), we can easily turn
1039 # to a per-vertex calculation if we want to implement some shading
1040 # technique. For an example see:
1041 # http://www.miralab.unige.ch/papers/368.pdf
1042 for f in mesh.faces:
1048 mat = materials[f.mat]
1050 # A new default material
1052 mat = Material.New('defMat')
1054 L = Vector(light_pos).normalize()
1056 V = (Vector(camPos) - Vector(f.v[0].co)).normalize()
1058 N = Vector(f.no).normalize()
1060 R = 2 * (N*L) * N - L
1062 # TODO: Attenuation factor (not used for now)
1063 a0 = 1; a1 = 0.0; a2 = 0.0
1064 d = (Vector(f.v[0].co) - Vector(light_pos)).length
1065 fd = min(1, 1.0/(a0 + a1*d + a2*d*d))
1069 ka = mat.getAmb() * Vector([0.1, 0.1, 0.1])
1072 # Diffuse component (add light.col for kd)
1073 kd = mat.getRef() * Vector(mat.getRGBCol())
1074 Ip = light.getEnergy()
1075 Idiff = Ip * kd * (N*L)
1077 # Specular component
1078 ks = mat.getSpec() * Vector(mat.getSpecCol())
1079 ns = mat.getHardness()
1080 Ispec = Ip * ks * pow((V * R), ns)
1082 # Emissive component
1083 ki = Vector([mat.getEmit()]*3)
1085 I = ki + Iamb + Idiff + Ispec
1087 # Set Alpha component
1089 I.append(mat.getAlpha())
1091 # Clamp I values between 0 and 1
1092 I = [ min(c, 1) for c in I]
1093 I = [ max(0, c) for c in I]
1094 tmp_col = [ int(c * 255.0) for c in I]
1102 def _doEdgesStyle(self, mesh, edgestyleSelect):
1103 """Process Mesh Edges accroding to a given selection style.
1105 Examples of algorithms:
1108 given an edge if its adjacent faces have the same normal (that is
1109 they are complanar), than deselect it.
1112 given an edge if one its adjacent faces is frontfacing and the
1113 other is backfacing, than select it, else deselect.
1116 Mesh.Mode(Mesh.SelectModes['EDGE'])
1118 for e in mesh.edges:
1120 if edgestyleSelect(e, mesh):
1125 def _doProjection(self, mesh, projector):
1126 """Calculate the Projection for the object.
1128 # TODO: maybe using the object.transform() can be faster?
1130 for v in mesh.verts:
1131 p = projector.doProjection(v.co)
1138 # ---------------------------------------------------------------------
1142 # ---------------------------------------------------------------------
1144 # A dictionary to collect all the different edge styles and their edge
1145 # selection criteria
1146 edgeSelectionStyles = {
1147 'normal': MeshUtils().isVisibleEdge,
1148 'silhouette': MeshUtils().isSilhouetteEdge
1151 # A dictionary to collect the supported output formats
1153 'SVG': SVGVectorWriter,
1157 # A wrapper function for the vectorizing process
1158 def vectorize(filename):
1159 """The vectorizing process is as follows:
1161 - Instanciate the writer and the renderer
1164 from Blender import Window
1165 editmode = Window.EditMode()
1166 if editmode: Window.EditMode(0)
1168 writer = outputWriters[OUTPUT_FORMAT](filename)
1170 renderer = Renderer()
1171 renderer.doRendering(writer, RENDER_ANIMATION)
1173 if editmode: Window.EditMode(1)
1177 if __name__ == "__main__":
1179 basename = Blender.sys.basename(Blender.Get('filename'))
1180 outputfile = Blender.sys.splitext(basename)[0]+".svg"
1182 if Blender.mode == 'background':
1183 vectorize(outputfile)
1185 label = "Save %s" % OUTPUT_FORMAT
1186 Blender.Window.FileSelector(vectorize, label, outputfile)