6 Tooltip: 'Vector Rendering Method script'
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://projects.blender.org/projects/vrm"]
11 __version__ = "0.3.beta"
14 Render the scene and save the result in vector format.
17 # ---------------------------------------------------------------------
18 # Copyright (c) 2006 Antonio Ospite
20 # This program is free software; you can redistribute it and/or modify
21 # it under the terms of the GNU General Public License as published by
22 # the Free Software Foundation; either version 2 of the License, or
23 # (at your option) any later version.
25 # This program is distributed in the hope that it will be useful,
26 # but WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 # GNU General Public License for more details.
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
34 # ---------------------------------------------------------------------
37 # Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
38 # Thanks to Nikola Radovanovic, the author of the original VRM script,
39 # the code you read here has been rewritten _almost_ entirely
40 # from scratch but Nikola gave me the idea, so I thank him publicly.
42 # ---------------------------------------------------------------------
44 # Things TODO for a next release:
45 # - FIX the issue with negative scales in object tranformations!
46 # - Use a better depth sorting algorithm
47 # - Review how selections are made (this script uses selection states of
48 # primitives to represent visibility infos)
49 # - Use a data structure other than Mesh to represent the 2D image?
50 # Think to a way to merge (adjacent) polygons that have the same color.
51 # Or a way to use paths for silhouettes and contours.
52 # - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
53 # not support SMIL for animations)
54 # - Switch to the Mesh structure, should be considerably faster
55 # (partially done, but with Mesh we cannot sort faces, yet)
56 # - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
57 # - Implement Shading Styles? (partially done, to make more flexible).
58 # - Add Vector Writers other than SVG.
59 # - set the background color!
60 # - Check memory use!!
62 # ---------------------------------------------------------------------
67 # * First release after code restucturing.
68 # Now the script offers a useful set of functionalities
69 # and it can render animations, too.
70 # * Optimization in Renderer.doEdgeStyle(), build a topology cache
71 # so to speed up the lookup of adjacent faces of an edge.
73 # * The SVG output is now SVG 1.0 valid.
74 # Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
75 # * Progress indicator during HSR.
76 # * Initial SWF output support (using ming)
77 # * Fixed a bug in the animation code, now the projection matrix is
78 # recalculated at each frame!
79 # * PDF output (using reportlab)
80 # * Fixed another problem in the animation code the current frame was off
81 # by one in the case of camera movement.
82 # * Use fps as specified in blender when VectorWriter handles animation
83 # * Remove the real file opening in the abstract VectorWriter
84 # * View frustum clipping
85 # * Scene clipping done using bounding box instead of object center
86 # * Fix camera type selection for blender>2.43 (Thanks to Thomas Lachmann)
87 # * Compatibility with python 2.3
88 # * Process only object that are on visible layers.
90 # ---------------------------------------------------------------------
93 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
94 from Blender.Mathutils import *
100 return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
101 # in python > 2.4 we ca use the following
102 #return [ u for u in alist if u not in locals()['_[1]'] ]
108 # We use a global progress Indicator Object
112 # Some global settings
116 polygons['SHOW'] = True
117 polygons['SHADING'] = 'FLAT' # FLAT or TOON
118 polygons['HSR'] = 'NEWELL' # PAINTER or NEWELL
119 # Hidden to the user for now
120 polygons['EXPANSION_TRICK'] = True
122 polygons['TOON_LEVELS'] = 2
125 edges['SHOW'] = False
126 edges['SHOW_HIDDEN'] = False
127 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
129 edges['COLOR'] = [0, 0, 0]
132 output['FORMAT'] = 'SVG'
133 output['ANIMATION'] = False
134 output['JOIN_OBJECTS'] = True
140 def dumpfaces(flist, filename):
141 """Dump a single face to a file.
152 writerobj = SVGVectorWriter(filename)
155 writerobj._printPolygons(m)
161 sys.stderr.write(msg)
164 return (abs(v1[0]-v2[0]) < EPS and
165 abs(v1[1]-v2[1]) < EPS )
166 by_furthest_z = (lambda f1, f2:
167 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
182 # ---------------------------------------------------------------------
186 # ---------------------------------------------------------------------
192 """A utility class for HSR processing.
195 def is_nonplanar_quad(face):
196 """Determine if a quad is non-planar.
198 From: http://mathworld.wolfram.com/Coplanar.html
200 Geometric objects lying in a common plane are said to be coplanar.
201 Three noncollinear points determine a plane and so are trivially coplanar.
202 Four points are coplanar iff the volume of the tetrahedron defined by them is
208 | x_4 y_4 z_4 1 | == 0
210 Coplanarity is equivalent to the statement that the pair of lines
211 determined by the four points are not skew, and can be equivalently stated
212 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
214 An arbitrary number of n points x_1, ..., x_n can be tested for
215 coplanarity by finding the point-plane distances of the points
216 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
217 and checking if they are all zero.
218 If so, the points are all coplanar.
220 We here check only for 4-point complanarity.
226 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
230 # three points must be complanar
233 x1 = Vector(face[0].co)
234 x2 = Vector(face[1].co)
235 x3 = Vector(face[2].co)
236 x4 = Vector(face[3].co)
238 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
244 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
246 def pointInPolygon(poly, v):
249 pointInPolygon = staticmethod(pointInPolygon)
251 def edgeIntersection(s1, s2, do_perturbate=False):
253 (x1, y1) = s1[0].co[0], s1[0].co[1]
254 (x2, y2) = s1[1].co[0], s1[1].co[1]
256 (x3, y3) = s2[0].co[0], s2[0].co[1]
257 (x4, y4) = s2[1].co[0], s2[1].co[1]
265 # calculate delta values (vector components)
274 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
275 if C == 0: #/* parallel */
278 dx3 = x1 - x3 # /* combined origin offset vector */
281 a1 = (dy3 * dx2 - dx3 * dy2) / C;
282 a2 = (dy3 * dx1 - dx3 * dy1) / C;
284 # check for degeneracies
286 #print_debug(str(a1)+"\n")
287 #print_debug(str(a2)+"\n\n")
289 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
290 # Intersection on boundaries, we consider the point external?
293 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
299 return (NMesh.Vert(x, y, z), a1, a2)
302 # lines have intersections but not those segments
305 edgeIntersection = staticmethod(edgeIntersection)
307 def isVertInside(self, v):
311 # Create point at infinity
312 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
314 for i in range(len(self.v)):
315 s1 = (point_at_infinity, v)
316 s2 = (self.v[i-1], self.v[i])
318 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
321 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
325 if winding_number % 2 == 0 :
332 isVertInside = staticmethod(isVertInside)
336 return ((b[0] - a[0]) * (c[1] - a[1]) -
337 (b[1] - a[1]) * (c[0] - a[0]) )
339 det = staticmethod(det)
341 def pointInPolygon(q, P):
344 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
348 for i in range(len(P.v)):
351 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
352 if det(p0.co, p1.co, q.co) == 0 :
355 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
360 pointInPolygon = staticmethod(pointInPolygon)
362 def projectionsOverlap(f1, f2):
363 """ If you have nonconvex, but still simple polygons, an acceptable method
364 is to iterate over all vertices and perform the Point-in-polygon test[1].
365 The advantage of this method is that you can compute the exact
366 intersection point and collision normal that you will need to simulate
367 collision. When you have the point that lies inside the other polygon, you
368 just iterate over all edges of the second polygon again and look for edge
369 intersections. Note that this method detects collsion when it already
370 happens. This algorithm is fast enough to perform it hundreds of times per
373 for i in range(len(f1.v)):
376 # If a point of f1 in inside f2, there is an overlap!
378 #if HSR.isVertInside(f2, v1):
379 if HSR.pointInPolygon(v1, f2):
382 # If not the polygon can be ovelap as well, so we check for
383 # intersection between an edge of f1 and all the edges of f2
387 for j in range(len(f2.v)):
394 intrs = HSR.edgeIntersection(e1, e2)
396 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
397 # str(v2.co) + " " + str(v3.co) )
398 #print_debug("\nIntersection\n")
404 projectionsOverlap = staticmethod(projectionsOverlap)
406 def midpoint(p1, p2):
407 """Return the midpoint of two vertices.
409 m = MidpointVecs(Vector(p1), Vector(p2))
410 mv = NMesh.Vert(m[0], m[1], m[2])
414 midpoint = staticmethod(midpoint)
416 def facesplit(P, Q, facelist, nmesh):
417 """Split P or Q according to the strategy illustrated in the Newell's
421 by_furthest_z = (lambda f1, f2:
422 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
425 # Choose if split P on Q plane or vice-versa
429 d = HSR.Distance(Vector(Pi), Q)
432 pIntersectQ = (n != len(P))
436 d = HSR.Distance(Vector(Qi), P)
439 qIntersectP = (n != len(Q))
443 # 1. If parts of P lie in both half-spaces of Q
444 # then splice P in two with the plane of Q
450 newfaces = HSR.splitOn(plane, f)
452 # 2. Else if parts of Q lie in both half-space of P
453 # then splice Q in two with the plane of P
454 if qIntersectP and newfaces == None:
459 newfaces = HSR.splitOn(plane, f)
462 # 3. Else slice P in half through the mid-point of
463 # the longest pair of opposite sides
466 print "We ignore P..."
473 # v1 = midpoint(f[0], f[1])
474 # v2 = midpoint(f[1], f[2])
476 # v1 = midpoint(f[0], f[1])
477 # v2 = midpoint(f[2], f[3])
478 #vec3 = (Vector(v2)+10*Vector(f.normal))
480 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
482 #plane = NMesh.Face([v1, v2, v3])
484 #newfaces = splitOn(plane, f)
488 print "Big FAT problem, we weren't able to split POLYGONS!"
494 # if v not in plane and v in nmesh.verts:
495 # nmesh.verts.remove(v)
500 nf.col = [f.col[0]] * len(nf.v)
505 nmesh.verts.append(v)
506 # insert pieces in the list
511 # and resort the faces
512 facelist.sort(by_furthest_z)
513 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
516 #print [ f.smooth for f in facelist ]
520 facesplit = staticmethod(facesplit)
522 def isOnSegment(v1, v2, p, extremes_internal=False):
523 """Check if point p is in segment v1v2.
529 # Should we consider extreme points as internal ?
531 # if p == v1 or p == v2:
532 if l1 < EPS or l2 < EPS:
533 return extremes_internal
537 # if the sum of l1 and l2 is circa l, then the point is on segment,
538 if abs(l - (l1+l2)) < EPS:
543 isOnSegment = staticmethod(isOnSegment)
545 def Distance(point, face):
546 """ Calculate the distance between a point and a face.
548 An alternative but more expensive method can be:
550 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
551 Vector(face.no), Vector(point), 0)
553 d = Vector(ip - point).length
555 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
559 plNormal = Vector(face.no)
560 plVert0 = Vector(face.v[0])
562 d = (plVert0 * plNormal) - (p * plNormal)
564 #d = plNormal * (plVert0 - p)
566 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
570 Distance = staticmethod(Distance)
574 # make one or two new faces based on a list of vertex-indices
603 makeFaces = staticmethod(makeFaces)
605 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
606 """Split P using the plane of Q.
607 Logic taken from the knife.py python script
610 # Check if P and Q are parallel
611 u = CrossVecs(Vector(Q.no),Vector(P.no))
617 print "PARALLEL planes!!"
621 # The final aim is to find the intersection line between P
622 # and the plane of Q, and split P along this line
626 # Calculate point-plane Distance between vertices of P and plane Q
628 for i in range(0, nP):
629 d.append(HSR.Distance(P.v[i], Q))
642 #print "d0:", d0, "d1:", d1
644 # if the vertex lies in the cutplane
646 #print "d1 On cutplane"
647 posVertList.append(V1)
648 negVertList.append(V1)
650 # if the previous vertex lies in cutplane
652 #print "d0 on Cutplane"
654 #print "d1 on positive Halfspace"
655 posVertList.append(V1)
657 #print "d1 on negative Halfspace"
658 negVertList.append(V1)
660 # if they are on the same side of the plane
662 #print "On the same half-space"
664 #print "d1 on positive Halfspace"
665 posVertList.append(V1)
667 #print "d1 on negative Halfspace"
668 negVertList.append(V1)
670 # the vertices are not on the same side of the plane, so we have an intersection
672 #print "Intersection"
674 e = Vector(V0), Vector(V1)
675 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
677 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
682 #print "Intersection", inters
684 nv = NMesh.Vert(inters[0], inters[1], inters[2])
685 newVertList.append(nv)
687 posVertList.append(nv)
688 negVertList.append(nv)
691 posVertList.append(V1)
693 negVertList.append(V1)
696 # uniq for python > 2.4
697 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
698 #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
700 # a more portable way
701 posVertList = uniq(posVertList)
702 negVertList = uniq(negVertList)
705 # If vertex are all on the same half-space, return
706 #if len(posVertList) < 3:
707 # print "Problem, we created a face with less that 3 vertices??"
709 #if len(negVertList) < 3:
710 # print "Problem, we created a face with less that 3 vertices??"
713 if len(posVertList) < 3 or len(negVertList) < 3:
714 #print "RETURN NONE, SURE???"
717 if not return_positive_faces:
719 if not return_negative_faces:
722 newfaces = HSR.addNewFaces(posVertList, negVertList)
726 splitOn = staticmethod(splitOn)
728 def addNewFaces(posVertList, negVertList):
729 # Create new faces resulting from the split
731 if len(posVertList) or len(negVertList):
733 #newfaces = [posVertList] + [negVertList]
734 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
735 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
739 outfaces += HSR.makeFaces(nf)
744 addNewFaces = staticmethod(addNewFaces)
747 # ---------------------------------------------------------------------
749 ## Mesh Utility class
751 # ---------------------------------------------------------------------
755 def buildEdgeFaceUsersCache(me):
757 Takes a mesh and returns a list aligned with the meshes edges.
758 Each item is a list of the faces that use the edge
759 would be the equiv for having ed.face_users as a property
761 Taken from .blender/scripts/bpymodules/BPyMesh.py,
762 thanks to ideasman_42.
765 def sorted_edge_indicies(ed):
773 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
775 fvi= [v.index for v in f.v]# face vert idx's
776 for i in xrange(len(f)):
783 face_edges_dict[i1,i2][1].append(f)
785 face_edges= [None] * len(me.edges)
786 for ed_index, ed_faces in face_edges_dict.itervalues():
787 face_edges[ed_index]= ed_faces
791 def isMeshEdge(adjacent_faces):
794 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
795 Note: if the edge has no adjacent faces we want to show it as well,
796 useful for "edge only" portion of objects.
799 if len(adjacent_faces) == 0:
802 selected_faces = [f for f in adjacent_faces if f.sel]
804 if len(selected_faces) != 0:
809 def isSilhouetteEdge(adjacent_faces):
810 """Silhuette selection rule.
812 An edge is a silhuette edge if it is shared by two faces with
813 different selection status or if it is a boundary edge of a selected
817 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
818 (len(adjacent_faces) == 2 and
819 adjacent_faces[0].sel != adjacent_faces[1].sel)
825 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
826 isMeshEdge = staticmethod(isMeshEdge)
827 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
830 # ---------------------------------------------------------------------
832 ## Shading Utility class
834 # ---------------------------------------------------------------------
840 def toonShadingMapSetup():
841 levels = config.polygons['TOON_LEVELS']
843 texels = 2*levels - 1
844 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
850 shademap = ShadingUtils.shademap
853 shademap = ShadingUtils.toonShadingMapSetup()
856 for i in xrange(0, len(shademap)-1):
857 pivot = (shademap[i]+shademap[i+1])/2.0
862 if v < shademap[i+1]:
867 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
868 toonShading = staticmethod(toonShading)
871 # ---------------------------------------------------------------------
873 ## Projections classes
875 # ---------------------------------------------------------------------
878 """Calculate the projection of an object given the camera.
880 A projector is useful to so some per-object transformation to obtain the
881 projection of an object given the camera.
883 The main method is #doProjection# see the method description for the
887 def __init__(self, cameraObj, canvasRatio):
888 """Calculate the projection matrix.
890 The projection matrix depends, in this case, on the camera settings.
891 TAKE CARE: This projector expects vertices in World Coordinates!
894 camera = cameraObj.getData()
896 aspect = float(canvasRatio[0])/float(canvasRatio[1])
897 near = camera.clipStart
900 scale = float(camera.scale)
902 fovy = atan(0.5/aspect/(camera.lens/32))
903 fovy = fovy * 360.0/pi
906 if Blender.Get('version') < 243:
913 # What projection do we want?
914 if camera.type == camPersp:
915 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
916 elif camera.type == camOrtho:
917 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
920 # View transformation
921 cam = Matrix(cameraObj.getInverseMatrix())
926 self.projectionMatrix = mP
932 def doProjection(self, v):
933 """Project the point on the view plane.
935 Given a vertex calculate the projection using the current projection
939 # Note that we have to work on the vertex using homogeneous coordinates
940 # From blender 2.42+ we don't need to resize the vector to be 4d
941 # when applying a 4x4 matrix, but we do that anyway since we need the
942 # 4th coordinate later
943 p = self.projectionMatrix * Vector(v).resize4D()
945 # Perspective division
962 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
963 """Return a perspective projection matrix.
966 top = near * tan(fovy * pi / 360.0)
970 x = (2.0 * near) / (right-left)
971 y = (2.0 * near) / (top-bottom)
972 a = (right+left) / (right-left)
973 b = (top+bottom) / (top - bottom)
974 c = - ((far+near) / (far-near))
975 d = - ((2*far*near)/(far-near))
981 [0.0, 0.0, -1.0, 0.0])
985 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
986 """Return an orthogonal projection matrix.
989 # The 11 in the formula was found emiprically
990 top = near * tan(fovy * pi / 360.0) * (scale * 11)
992 left = bottom * aspect
997 tx = -((right+left)/rl)
998 ty = -((top+bottom)/tb)
1002 [2.0/rl, 0.0, 0.0, tx],
1003 [0.0, 2.0/tb, 0.0, ty],
1004 [0.0, 0.0, 2.0/fn, tz],
1005 [0.0, 0.0, 0.0, 1.0])
1010 # ---------------------------------------------------------------------
1012 ## Progress Indicator
1014 # ---------------------------------------------------------------------
1017 """A model for a progress indicator.
1019 Do the progress calculation calculation and
1020 the view independent stuff of a progress indicator.
1022 def __init__(self, steps=0):
1028 def setSteps(self, steps):
1029 """Set the number of steps of the activity wich we want to track.
1036 def setName(self, name):
1037 """Set the name of the activity wich we want to track.
1044 def getProgress(self):
1045 return self.progress
1052 """Update the model, call this method when one step is completed.
1054 if self.progress == 100:
1058 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1059 self.progress = int(self.progress)
1064 class ProgressIndicator:
1065 """An abstraction of a View for the Progress Model
1069 # Use a refresh rate so we do not show the progress at
1070 # every update, but every 'self.refresh_rate' times.
1071 self.refresh_rate = 10
1072 self.shows_counter = 0
1076 self.progressModel = None
1078 def setQuiet(self, value):
1081 def setActivity(self, name, steps):
1082 """Initialize the Model.
1084 In a future version (with subactivities-progress support) this method
1085 could only set the current activity.
1087 self.progressModel = Progress()
1088 self.progressModel.setName(name)
1089 self.progressModel.setSteps(steps)
1091 def getActivity(self):
1092 return self.progressModel
1095 """Update the model and show the actual progress.
1097 assert(self.progressModel)
1099 if self.progressModel.update():
1103 self.show(self.progressModel.getProgress(),
1104 self.progressModel.getName())
1106 # We return always True here so we can call the update() method also
1107 # from lambda funcs (putting the call in logical AND with other ops)
1110 def show(self, progress, name=""):
1111 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1112 if self.shows_counter != 0:
1116 self.shows_counter = -1
1119 class ConsoleProgressIndicator(ProgressIndicator):
1120 """Show a progress bar on stderr, a la wget.
1123 ProgressIndicator.__init__(self)
1125 self.swirl_chars = ["-", "\\", "|", "/"]
1126 self.swirl_count = -1
1128 def show(self, progress, name):
1129 ProgressIndicator.show(self, progress, name)
1132 bar_progress = int( (progress/100.0) * bar_length )
1133 bar = ("=" * bar_progress).ljust(bar_length)
1135 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1136 swirl_char = self.swirl_chars[self.swirl_count]
1138 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1140 sys.stderr.write(progress_bar+"\r")
1142 sys.stderr.write("\n")
1145 class GraphicalProgressIndicator(ProgressIndicator):
1146 """Interface to the Blender.Window.DrawProgressBar() method.
1149 ProgressIndicator.__init__(self)
1151 #self.swirl_chars = ["-", "\\", "|", "/"]
1152 # We have to use letters with the same width, for now!
1153 # Blender progress bar considers the font widths when
1154 # calculating the progress bar width.
1155 self.swirl_chars = ["\\", "/"]
1156 self.swirl_count = -1
1158 def show(self, progress, name):
1159 ProgressIndicator.show(self, progress)
1161 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1162 swirl_char = self.swirl_chars[self.swirl_count]
1164 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1166 # Finally draw the Progress Bar
1167 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1168 Window.DrawProgressBar(progress/100.0, progress_text)
1171 Window.DrawProgressBar(1, progress_text)
1172 Window.WaitCursor(0)
1176 # ---------------------------------------------------------------------
1178 ## 2D Object representation class
1180 # ---------------------------------------------------------------------
1182 # TODO: a class to represent the needed properties of a 2D vector image
1183 # For now just using a [N]Mesh structure.
1186 # ---------------------------------------------------------------------
1188 ## Vector Drawing Classes
1190 # ---------------------------------------------------------------------
1196 A class for printing output in a vectorial format.
1198 Given a 2D representation of the 3D scene the class is responsible to
1199 write it is a vector format.
1201 Every subclasses of VectorWriter must have at last the following public
1205 - printCanvas(self, scene,
1206 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1209 def __init__(self, fileName):
1210 """Set the output file name and other properties"""
1212 self.outputFileName = fileName
1214 context = Scene.GetCurrent().getRenderingContext()
1215 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1217 self.fps = context.fps
1221 self.animation = False
1228 def open(self, startFrame=1, endFrame=1):
1229 if startFrame != endFrame:
1230 self.startFrame = startFrame
1231 self.endFrame = endFrame
1232 self.animation = True
1234 print "Outputting to: ", self.outputFileName
1241 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1242 showHiddenEdges=False):
1243 """This is the interface for the needed printing routine.
1250 class SVGVectorWriter(VectorWriter):
1251 """A concrete class for writing SVG output.
1254 def __init__(self, fileName):
1255 """Simply call the parent Contructor.
1257 VectorWriter.__init__(self, fileName)
1266 def open(self, startFrame=1, endFrame=1):
1267 """Do some initialization operations.
1269 VectorWriter.open(self, startFrame, endFrame)
1271 self.file = open(self.outputFileName, "w")
1276 """Do some finalization operation.
1283 # remember to call the close method of the parent as last
1284 VectorWriter.close(self)
1287 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1288 showHiddenEdges=False):
1289 """Convert the scene representation to SVG.
1292 Objects = scene.getChildren()
1294 context = scene.getRenderingContext()
1295 framenumber = context.currentFrame()
1298 framestyle = "display:none"
1300 framestyle = "display:block"
1302 # Assign an id to this group so we can set properties on it using DOM
1303 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1304 (framenumber, framestyle) )
1309 if(obj.getType() != 'Mesh'):
1312 self.file.write("<g id=\"%s\">\n" % obj.getName())
1314 mesh = obj.getData(mesh=1)
1317 self._printPolygons(mesh)
1320 self._printEdges(mesh, showHiddenEdges)
1322 self.file.write("</g>\n")
1324 self.file.write("</g>\n")
1331 def _calcCanvasCoord(self, v):
1332 """Convert vertex in scene coordinates to canvas coordinates.
1335 pt = Vector([0, 0, 0])
1337 mW = float(self.canvasSize[0])/2.0
1338 mH = float(self.canvasSize[1])/2.0
1340 # rescale to canvas size
1341 pt[0] = v.co[0]*mW + mW
1342 pt[1] = v.co[1]*mH + mH
1345 # For now we want (0,0) in the top-left corner of the canvas.
1346 # Mirror and translate along y
1348 pt[1] += self.canvasSize[1]
1352 def _printHeader(self):
1353 """Print SVG header."""
1355 self.file.write("<?xml version=\"1.0\"?>\n")
1356 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1357 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1358 self.file.write("<svg version=\"1.0\"\n")
1359 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1360 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1364 delay = 1000/self.fps
1366 self.file.write("""\n<script type="text/javascript"><![CDATA[
1367 globalStartFrame=%d;
1370 timerID = setInterval("NextFrame()", %d);
1371 globalFrameCounter=%d;
1372 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1374 self.file.write("""\n
1375 function NextFrame()
1377 currentElement = document.getElementById('frame'+globalFrameCounter)
1378 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1380 if (!currentElement)
1385 if (globalFrameCounter > globalEndFrame)
1387 clearInterval(timerID)
1393 previousElement.style.display="none";
1395 currentElement.style.display="block";
1396 globalFrameCounter++;
1402 def _printFooter(self):
1403 """Print the SVG footer."""
1405 self.file.write("\n</svg>\n")
1407 def _printPolygons(self, mesh):
1408 """Print the selected (visible) polygons.
1411 if len(mesh.faces) == 0:
1414 self.file.write("<g>\n")
1416 for face in mesh.faces:
1420 self.file.write("<path d=\"")
1422 #p = self._calcCanvasCoord(face.verts[0])
1423 p = self._calcCanvasCoord(face.v[0])
1424 self.file.write("M %g,%g L " % (p[0], p[1]))
1426 for v in face.v[1:]:
1427 p = self._calcCanvasCoord(v)
1428 self.file.write("%g,%g " % (p[0], p[1]))
1430 # get rid of the last blank space, just cosmetics here.
1431 self.file.seek(-1, 1)
1432 self.file.write(" z\"\n")
1434 # take as face color the first vertex color
1437 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1439 color = [255, 255, 255, 255]
1441 # Convert the color to the #RRGGBB form
1442 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1444 # Handle transparent polygons
1447 opacity = float(color[3])/255.0
1448 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1449 #opacity_string = "opacity: %g;" % (opacity)
1451 self.file.write("\tstyle=\"fill:" + str_col + ";")
1452 self.file.write(opacity_string)
1454 # use the stroke property to alleviate the "adjacent edges" problem,
1455 # we simulate polygon expansion using borders,
1456 # see http://www.antigrain.com/svg/index.html for more info
1459 # EXPANSION TRICK is not that useful where there is transparency
1460 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1461 # str_col = "#000000" # For debug
1462 self.file.write(" stroke:%s;\n" % str_col)
1463 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1464 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1466 self.file.write("\"/>\n")
1468 self.file.write("</g>\n")
1470 def _printEdges(self, mesh, showHiddenEdges=False):
1471 """Print the wireframe using mesh edges.
1474 stroke_width = config.edges['WIDTH']
1475 stroke_col = config.edges['COLOR']
1477 self.file.write("<g>\n")
1479 for e in mesh.edges:
1481 hidden_stroke_style = ""
1484 if showHiddenEdges == False:
1487 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1489 p1 = self._calcCanvasCoord(e.v1)
1490 p2 = self._calcCanvasCoord(e.v2)
1492 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1493 % ( p1[0], p1[1], p2[0], p2[1] ) )
1494 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1495 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1496 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1497 self.file.write(hidden_stroke_style)
1498 self.file.write("\"/>\n")
1500 self.file.write("</g>\n")
1509 SWFSupported = False
1511 class SWFVectorWriter(VectorWriter):
1512 """A concrete class for writing SWF output.
1515 def __init__(self, fileName):
1516 """Simply call the parent Contructor.
1518 VectorWriter.__init__(self, fileName)
1528 def open(self, startFrame=1, endFrame=1):
1529 """Do some initialization operations.
1531 VectorWriter.open(self, startFrame, endFrame)
1532 self.movie = SWFMovie()
1533 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1535 self.movie.setRate(self.fps)
1536 numframes = endFrame - startFrame + 1
1537 self.movie.setFrames(numframes)
1540 """Do some finalization operation.
1542 self.movie.save(self.outputFileName)
1544 # remember to call the close method of the parent
1545 VectorWriter.close(self)
1547 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1548 showHiddenEdges=False):
1549 """Convert the scene representation to SVG.
1551 context = scene.getRenderingContext()
1552 framenumber = context.currentFrame()
1554 Objects = scene.getChildren()
1557 self.movie.remove(self.sprite)
1559 sprite = SWFSprite()
1563 if(obj.getType() != 'Mesh'):
1566 mesh = obj.getData(mesh=1)
1569 self._printPolygons(mesh, sprite)
1572 self._printEdges(mesh, sprite, showHiddenEdges)
1575 i = self.movie.add(sprite)
1576 # Remove the instance the next time
1579 self.movie.nextFrame()
1586 def _calcCanvasCoord(self, v):
1587 """Convert vertex in scene coordinates to canvas coordinates.
1590 pt = Vector([0, 0, 0])
1592 mW = float(self.canvasSize[0])/2.0
1593 mH = float(self.canvasSize[1])/2.0
1595 # rescale to canvas size
1596 pt[0] = v.co[0]*mW + mW
1597 pt[1] = v.co[1]*mH + mH
1600 # For now we want (0,0) in the top-left corner of the canvas.
1601 # Mirror and translate along y
1603 pt[1] += self.canvasSize[1]
1607 def _printPolygons(self, mesh, sprite):
1608 """Print the selected (visible) polygons.
1611 if len(mesh.faces) == 0:
1614 for face in mesh.faces:
1620 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1622 color = [255, 255, 255, 255]
1625 f = s.addFill(color[0], color[1], color[2], color[3])
1628 # The starting point of the shape
1629 p0 = self._calcCanvasCoord(face.verts[0])
1630 s.movePenTo(p0[0], p0[1])
1632 for v in face.verts[1:]:
1633 p = self._calcCanvasCoord(v)
1634 s.drawLineTo(p[0], p[1])
1637 s.drawLineTo(p0[0], p0[1])
1643 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1644 """Print the wireframe using mesh edges.
1647 stroke_width = config.edges['WIDTH']
1648 stroke_col = config.edges['COLOR']
1652 for e in mesh.edges:
1654 # Next, we set the line width and color for our shape.
1655 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1659 if showHiddenEdges == False:
1662 # SWF does not support dashed lines natively, so -for now-
1663 # draw hidden lines thinner and half-trasparent
1664 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1667 p1 = self._calcCanvasCoord(e.v1)
1668 p2 = self._calcCanvasCoord(e.v2)
1670 s.movePenTo(p1[0], p1[1])
1671 s.drawLineTo(p2[0], p2[1])
1680 from reportlab.pdfgen import canvas
1683 PDFSupported = False
1685 class PDFVectorWriter(VectorWriter):
1686 """A concrete class for writing PDF output.
1689 def __init__(self, fileName):
1690 """Simply call the parent Contructor.
1692 VectorWriter.__init__(self, fileName)
1701 def open(self, startFrame=1, endFrame=1):
1702 """Do some initialization operations.
1704 VectorWriter.open(self, startFrame, endFrame)
1705 size = (self.canvasSize[0], self.canvasSize[1])
1706 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1709 """Do some finalization operation.
1713 # remember to call the close method of the parent
1714 VectorWriter.close(self)
1716 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1717 showHiddenEdges=False):
1718 """Convert the scene representation to SVG.
1720 context = scene.getRenderingContext()
1721 framenumber = context.currentFrame()
1723 Objects = scene.getChildren()
1727 if(obj.getType() != 'Mesh'):
1730 mesh = obj.getData(mesh=1)
1733 self._printPolygons(mesh)
1736 self._printEdges(mesh, showHiddenEdges)
1738 self.canvas.showPage()
1744 def _calcCanvasCoord(self, v):
1745 """Convert vertex in scene coordinates to canvas coordinates.
1748 pt = Vector([0, 0, 0])
1750 mW = float(self.canvasSize[0])/2.0
1751 mH = float(self.canvasSize[1])/2.0
1753 # rescale to canvas size
1754 pt[0] = v.co[0]*mW + mW
1755 pt[1] = v.co[1]*mH + mH
1758 # For now we want (0,0) in the top-left corner of the canvas.
1759 # Mirror and translate along y
1761 pt[1] += self.canvasSize[1]
1765 def _printPolygons(self, mesh):
1766 """Print the selected (visible) polygons.
1769 if len(mesh.faces) == 0:
1772 for face in mesh.faces:
1778 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1781 color = [1, 1, 1, 1]
1783 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1785 self.canvas.setStrokeColorRGB(0, 0, 0)
1787 path = self.canvas.beginPath()
1789 # The starting point of the path
1790 p0 = self._calcCanvasCoord(face.verts[0])
1791 path.moveTo(p0[0], p0[1])
1793 for v in face.verts[1:]:
1794 p = self._calcCanvasCoord(v)
1795 path.lineTo(p[0], p[1])
1800 self.canvas.drawPath(path, stroke=0, fill=1)
1802 def _printEdges(self, mesh, showHiddenEdges=False):
1803 """Print the wireframe using mesh edges.
1806 stroke_width = config.edges['WIDTH']
1807 stroke_col = config.edges['COLOR']
1809 self.canvas.setLineCap(1)
1810 self.canvas.setLineJoin(1)
1811 self.canvas.setLineWidth(stroke_width)
1812 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1815 for e in mesh.edges:
1817 self.canvas.setLineWidth(stroke_width)
1820 if showHiddenEdges == False:
1823 # PDF does not support dashed lines natively, so -for now-
1824 # draw hidden lines thinner
1825 self.canvas.setLineWidth(stroke_width/2.0)
1827 p1 = self._calcCanvasCoord(e.v1)
1828 p2 = self._calcCanvasCoord(e.v2)
1830 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1834 # ---------------------------------------------------------------------
1836 ## Rendering Classes
1838 # ---------------------------------------------------------------------
1840 # A dictionary to collect different shading style methods
1841 shadingStyles = dict()
1842 shadingStyles['FLAT'] = None
1843 shadingStyles['TOON'] = None
1845 # A dictionary to collect different edge style methods
1847 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1848 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1850 # A dictionary to collect the supported output formats
1851 outputWriters = dict()
1852 outputWriters['SVG'] = SVGVectorWriter
1854 outputWriters['SWF'] = SWFVectorWriter
1856 outputWriters['PDF'] = PDFVectorWriter
1860 """Render a scene viewed from the active camera.
1862 This class is responsible of the rendering process, transformation and
1863 projection of the objects in the scene are invoked by the renderer.
1865 The rendering is done using the active camera for the current scene.
1869 """Make the rendering process only for the current scene by default.
1871 We will work on a copy of the scene, to be sure that the current scene do
1872 not get modified in any way.
1875 # Render the current Scene, this should be a READ-ONLY property
1876 self._SCENE = Scene.GetCurrent()
1878 # Use the aspect ratio of the scene rendering context
1879 context = self._SCENE.getRenderingContext()
1881 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1882 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1883 float(context.aspectRatioY())
1886 # Render from the currently active camera
1887 #self.cameraObj = self._SCENE.getCurrentCamera()
1896 def doRendering(self, outputWriter, animation=False):
1897 """Render picture or animation and write it out.
1900 - a Vector writer object that will be used to output the result.
1901 - a flag to tell if we want to render an animation or only the
1905 context = self._SCENE.getRenderingContext()
1906 origCurrentFrame = context.currentFrame()
1908 # Handle the animation case
1910 startFrame = origCurrentFrame
1911 endFrame = startFrame
1914 startFrame = context.startFrame()
1915 endFrame = context.endFrame()
1916 outputWriter.open(startFrame, endFrame)
1918 # Do the rendering process frame by frame
1919 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1920 for f in xrange(startFrame, endFrame+1):
1921 print "\n\nFrame: %d" % f
1923 # FIXME To get the correct camera position we have to use +1 here.
1924 # Is there a bug somewhere in the Scene module?
1925 context.currentFrame(f+1)
1926 self.cameraObj = self._SCENE.getCurrentCamera()
1928 # Use some temporary workspace, a full copy of the scene
1929 inputScene = self._SCENE.copy(2)
1931 # To get the objects at this frame remove the +1 ...
1932 ctx = inputScene.getRenderingContext()
1936 # Get a projector for this camera.
1937 # NOTE: the projector wants object in world coordinates,
1938 # so we should remember to apply modelview transformations
1939 # _before_ we do projection transformations.
1940 self.proj = Projector(self.cameraObj, self.canvasRatio)
1943 renderedScene = self.doRenderScene(inputScene)
1945 print "There was an error! Aborting."
1947 print traceback.print_exc()
1949 self._SCENE.makeCurrent()
1950 Scene.unlink(inputScene)
1954 outputWriter.printCanvas(renderedScene,
1955 doPrintPolygons = config.polygons['SHOW'],
1956 doPrintEdges = config.edges['SHOW'],
1957 showHiddenEdges = config.edges['SHOW_HIDDEN'])
1959 # delete the rendered scene
1960 self._SCENE.makeCurrent()
1961 Scene.unlink(renderedScene)
1964 outputWriter.close()
1966 context.currentFrame(origCurrentFrame)
1969 def doRenderScene(self, workScene):
1970 """Control the rendering process.
1972 Here we control the entire rendering process invoking the operation
1973 needed to transform and project the 3D scene in two dimensions.
1976 # global processing of the scene
1978 self._filterHiddenObjects(workScene)
1980 self._buildLightSetup(workScene)
1982 self._doSceneClipping(workScene)
1984 self._doConvertGeometricObjsToMesh(workScene)
1986 if config.output['JOIN_OBJECTS']:
1987 self._joinMeshObjectsInScene(workScene)
1989 self._doSceneDepthSorting(workScene)
1991 # Per object activities
1993 Objects = workScene.getChildren()
1995 print "Total Objects: %d" % len(Objects)
1996 for i,obj in enumerate(Objects):
1998 print "Rendering Object: %d" % i
2000 if obj.getType() != 'Mesh':
2001 print "Only Mesh supported! - Skipping type:", obj.getType()
2004 print "Rendering: ", obj.getName()
2006 mesh = obj.getData(mesh=1)
2008 self._doModelingTransformation(mesh, obj.matrix)
2010 self._doBackFaceCulling(mesh)
2013 # When doing HSR with NEWELL we may want to flip all normals
2015 if config.polygons['HSR'] == "NEWELL":
2016 for f in mesh.faces:
2019 for f in mesh.faces:
2022 self._doLighting(mesh)
2024 # Do "projection" now so we perform further processing
2025 # in Normalized View Coordinates
2026 self._doProjection(mesh, self.proj)
2028 self._doViewFrustumClipping(mesh)
2030 self._doHiddenSurfaceRemoval(mesh)
2032 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2034 # Update the object data, important! :)
2046 def _getObjPosition(self, obj):
2047 """Return the obj position in World coordinates.
2049 return obj.matrix.translationPart()
2051 def _cameraViewVector(self):
2052 """Get the View Direction form the camera matrix.
2054 return Vector(self.cameraObj.matrix[2]).resize3D()
2059 def _isFaceVisible(self, face):
2060 """Determine if a face of an object is visible from the current camera.
2062 The view vector is calculated from the camera location and one of the
2063 vertices of the face (expressed in World coordinates, after applying
2064 modelview transformations).
2066 After those transformations we determine if a face is visible by
2067 computing the angle between the face normal and the view vector, this
2068 angle has to be between -90 and 90 degrees for the face to be visible.
2069 This corresponds somehow to the dot product between the two, if it
2070 results > 0 then the face is visible.
2072 There is no need to normalize those vectors since we are only interested in
2073 the sign of the cross product and not in the product value.
2075 NOTE: here we assume the face vertices are in WorldCoordinates, so
2076 please transform the object _before_ doing the test.
2079 normal = Vector(face.no)
2080 camPos = self._getObjPosition(self.cameraObj)
2083 # View Vector in orthographics projections is the view Direction of
2085 if self.cameraObj.data.getType() == 1:
2086 view_vect = self._cameraViewVector()
2088 # View vector in perspective projections can be considered as
2089 # the difference between the camera position and one point of
2090 # the face, we choose the farthest point from the camera.
2091 if self.cameraObj.data.getType() == 0:
2092 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2096 # if d > 0 the face is visible from the camera
2097 d = view_vect * normal
2107 def _filterHiddenObjects(self, scene):
2108 """Discard object that are on hidden layers in the scene.
2111 Objects = scene.getChildren()
2113 visible_obj_list = [ obj for obj in Objects if
2114 set(obj.layers).intersection(set(scene.getLayers())) ]
2117 if o not in visible_obj_list:
2124 def _buildLightSetup(self, scene):
2125 # Get the list of lighting sources
2126 obj_lst = scene.getChildren()
2127 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp' ]
2129 # When there are no lights we use a default lighting source
2130 # that have the same position of the camera
2131 if len(self.lights) == 0:
2132 l = Lamp.New('Lamp')
2133 lobj = Object.New('Lamp')
2134 lobj.loc = self.cameraObj.loc
2136 self.lights.append(lobj)
2139 def _doSceneClipping(self, scene):
2140 """Clip whole objects against the View Frustum.
2142 For now clip away only objects according to their center position.
2145 cam_pos = self._getObjPosition(self.cameraObj)
2146 view_vect = self._cameraViewVector()
2148 near = self.cameraObj.data.clipStart
2149 far = self.cameraObj.data.clipEnd
2151 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2152 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2153 fovy = fovy * 360.0/pi
2155 Objects = scene.getChildren()
2158 if o.getType() != 'Mesh': continue;
2161 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2163 d = obj_vect*view_vect
2164 theta = AngleBetweenVecs(obj_vect, view_vect)
2166 # if the object is outside the view frustum, clip it away
2167 if (d < near) or (d > far) or (theta > fovy):
2171 # Use the object bounding box
2172 # (whose points are already in WorldSpace Coordinate)
2174 bb = o.getBoundBox()
2178 p_vect = Vector(cam_pos) - Vector(p)
2180 d = p_vect * view_vect
2181 theta = AngleBetweenVecs(p_vect, view_vect)
2183 # Is this point outside the view frustum?
2184 if (d < near) or (d > far) or (theta > fovy):
2187 # If the bb is all outside the view frustum we clip the whole
2189 if points_outside == len(bb):
2194 def _doConvertGeometricObjsToMesh(self, scene):
2195 """Convert all "geometric" objects to mesh ones.
2197 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2198 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2200 Objects = scene.getChildren()
2202 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2205 obj = self._convertToRawMeshObj(obj)
2207 scene.unlink(old_obj)
2210 # XXX Workaround for Text and Curve which have some normals
2211 # inverted when they are converted to Mesh, REMOVE that when
2212 # blender will fix that!!
2213 if old_obj.getType() in ['Curve', 'Text']:
2214 me = obj.getData(mesh=1)
2215 for f in me.faces: f.sel = 1;
2216 for v in me.verts: v.sel = 1;
2223 def _doSceneDepthSorting(self, scene):
2224 """Sort objects in the scene.
2226 The object sorting is done accordingly to the object centers.
2229 c = self._getObjPosition(self.cameraObj)
2231 by_obj_center_pos = (lambda o1, o2:
2232 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2233 cmp((self._getObjPosition(o1) - Vector(c)).length,
2234 (self._getObjPosition(o2) - Vector(c)).length)
2237 # Implement sorting by bounding box, the object with the bb
2238 # nearest to the camera should be drawn as last.
2239 by_nearest_bbox_point = (lambda o1, o2:
2240 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2241 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2242 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2247 Objects = scene.getChildren()
2249 #Objects.sort(by_obj_center_pos)
2250 Objects.sort(by_nearest_bbox_point)
2257 def _joinMeshObjectsInScene(self, scene):
2258 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2261 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
2263 # FIXME: Object.join() do not work if the list contains 1 object
2267 mesh = Mesh.New('BigOne')
2268 bigObj = Object.New('Mesh', 'BigOne')
2275 except RuntimeError:
2276 print "\nWarning! - Can't Join Objects\n"
2277 scene.unlink(bigObj)
2280 print "Objects Type error?"
2288 # Per object/mesh methods
2290 def _convertToRawMeshObj(self, object):
2291 """Convert geometry based object to a mesh object.
2293 me = Mesh.New('RawMesh_'+object.name)
2294 me.getFromObject(object.name)
2296 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2299 # If the object has no materials set a default material
2300 if not me.materials:
2301 me.materials = [Material.New()]
2302 #for f in me.faces: f.mat = 0
2304 newObject.setMatrix(object.getMatrix())
2308 def _doModelingTransformation(self, mesh, matrix):
2309 """Transform object coordinates to world coordinates.
2311 This step is done simply applying to the object its tranformation
2312 matrix and recalculating its normals.
2314 # XXX FIXME: blender do not transform normals in the right way when
2315 # there are negative scale values
2316 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2317 print "WARNING: Negative scales, expect incorrect results!"
2319 mesh.transform(matrix, True)
2321 def _doBackFaceCulling(self, mesh):
2322 """Simple Backface Culling routine.
2324 At this level we simply do a visibility test face by face and then
2325 select the vertices belonging to visible faces.
2328 # Select all vertices, so edges can be displayed even if there are no
2330 for v in mesh.verts:
2333 Mesh.Mode(Mesh.SelectModes['FACE'])
2335 for f in mesh.faces:
2337 if self._isFaceVisible(f):
2340 def _doLighting(self, mesh):
2341 """Apply an Illumination and shading model to the object.
2343 The model used is the Phong one, it may be inefficient,
2344 but I'm just learning about rendering and starting from Phong seemed
2345 the most natural way.
2348 # If the mesh has vertex colors already, use them,
2349 # otherwise turn them on and do some calculations
2350 if mesh.vertexColors:
2352 mesh.vertexColors = 1
2354 materials = mesh.materials
2356 camPos = self._getObjPosition(self.cameraObj)
2358 # We do per-face color calculation (FLAT Shading), we can easily turn
2359 # to a per-vertex calculation if we want to implement some shading
2360 # technique. For an example see:
2361 # http://www.miralab.unige.ch/papers/368.pdf
2362 for f in mesh.faces:
2368 mat = materials[f.mat]
2370 # A new default material
2372 mat = Material.New('defMat')
2374 # Check if it is a shadeless material
2375 elif mat.getMode() & Material.Modes['SHADELESS']:
2377 # Convert to a value between 0 and 255
2378 tmp_col = [ int(c * 255.0) for c in I]
2389 # do vertex color calculation
2391 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2393 for l in self.lights:
2395 light_pos = self._getObjPosition(l)
2396 light = light_obj.getData()
2398 L = Vector(light_pos).normalize()
2400 V = (Vector(camPos) - Vector(f.cent)).normalize()
2402 N = Vector(f.no).normalize()
2404 if config.polygons['SHADING'] == 'TOON':
2405 NL = ShadingUtils.toonShading(N*L)
2409 # Should we use NL instead of (N*L) here?
2410 R = 2 * (N*L) * N - L
2412 Ip = light.getEnergy()
2414 # Diffuse co-efficient
2415 kd = mat.getRef() * Vector(mat.getRGBCol())
2417 kd[i] *= light.col[i]
2419 Idiff = Ip * kd * max(0, NL)
2422 # Specular component
2423 ks = mat.getSpec() * Vector(mat.getSpecCol())
2424 ns = mat.getHardness()
2425 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2427 TotDiffSpec += (Idiff+Ispec)
2431 Iamb = Vector(Blender.World.Get()[0].getAmb())
2434 # Emissive component (convert to a triplet)
2435 ki = Vector([mat.getEmit()]*3)
2437 #I = ki + Iamb + (Idiff + Ispec)
2438 I = ki + (ka * Iamb) + TotDiffSpec
2441 # Set Alpha component
2443 I.append(mat.getAlpha())
2445 # Clamp I values between 0 and 1
2446 I = [ min(c, 1) for c in I]
2447 I = [ max(0, c) for c in I]
2449 # Convert to a value between 0 and 255
2450 tmp_col = [ int(c * 255.0) for c in I]
2458 def _doProjection(self, mesh, projector):
2459 """Apply Viewing and Projection tranformations.
2462 for v in mesh.verts:
2463 p = projector.doProjection(v.co[:])
2468 #mesh.recalcNormals()
2471 # We could reeset Camera matrix, since now
2472 # we are in Normalized Viewing Coordinates,
2473 # but doung that would affect World Coordinate
2474 # processing for other objects
2476 #self.cameraObj.data.type = 1
2477 #self.cameraObj.data.scale = 2.0
2478 #m = Matrix().identity()
2479 #self.cameraObj.setMatrix(m)
2481 def _doViewFrustumClipping(self, mesh):
2482 """Clip faces against the View Frustum.
2485 # The Canonical View Volume, 8 vertices, and 6 faces,
2486 # We consider its face normals pointing outside
2488 v1 = NMesh.Vert(1, 1, -1)
2489 v2 = NMesh.Vert(1, -1, -1)
2490 v3 = NMesh.Vert(-1, -1, -1)
2491 v4 = NMesh.Vert(-1, 1, -1)
2492 v5 = NMesh.Vert(1, 1, 1)
2493 v6 = NMesh.Vert(1, -1, 1)
2494 v7 = NMesh.Vert(-1, -1, 1)
2495 v8 = NMesh.Vert(-1, 1, 1)
2498 f1 = NMesh.Face([v1, v4, v3, v2])
2500 f2 = NMesh.Face([v5, v6, v7, v8])
2502 f3 = NMesh.Face([v1, v2, v6, v5])
2504 f4 = NMesh.Face([v2, v3, v7, v6])
2506 f5 = NMesh.Face([v3, v4, v8, v7])
2508 f6 = NMesh.Face([v4, v1, v5, v8])
2511 nmesh = NMesh.GetRaw(mesh.name)
2512 clippedfaces = nmesh.faces[:]
2513 facelist = clippedfaces[:]
2515 for clipface in cvv:
2521 newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2524 # Check if the face is all outside the view frustum
2525 # TODO: Do this test before, it is more efficient
2528 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2531 if points_outside != len(f):
2532 clippedfaces.append(f)
2536 nmesh.verts.append(v)
2540 nf.col = [f.col[0]] * len(nf.v)
2542 clippedfaces.append(nf)
2543 facelist = clippedfaces[:]
2546 nmesh.faces = facelist
2551 def __simpleDepthSort(self, mesh):
2552 """Sort faces by the furthest vertex.
2554 This simple mesthod is known also as the painter algorithm, and it
2555 solves HSR correctly only for convex meshes.
2560 # The sorting requires circa n*log(n) steps
2562 progress.setActivity("HSR: Painter", n*log(n))
2564 by_furthest_z = (lambda f1, f2: progress.update() and
2565 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2568 # FIXME: using NMesh to sort faces. We should avoid that!
2569 nmesh = NMesh.GetRaw(mesh.name)
2571 # remember that _higher_ z values mean further points
2572 nmesh.faces.sort(by_furthest_z)
2573 nmesh.faces.reverse()
2578 def __newellDepthSort(self, mesh):
2579 """Newell's depth sorting.
2585 # Find non planar quads and convert them to triangle
2586 #for f in mesh.faces:
2588 # if is_nonplanar_quad(f.v):
2589 # print "NON QUAD??"
2593 # Now reselect all faces
2594 for f in mesh.faces:
2596 mesh.quadToTriangle()
2598 # FIXME: using NMesh to sort faces. We should avoid that!
2599 nmesh = NMesh.GetRaw(mesh.name)
2601 # remember that _higher_ z values mean further points
2602 nmesh.faces.sort(by_furthest_z)
2603 nmesh.faces.reverse()
2605 # Begin depth sort tests
2607 # use the smooth flag to set marked faces
2608 for f in nmesh.faces:
2611 facelist = nmesh.faces[:]
2615 # The steps are _at_least_ equal to len(facelist), we do not count the
2616 # feces coming out from splitting!!
2617 progress.setActivity("HSR: Newell", len(facelist))
2618 #progress.setQuiet(True)
2621 while len(facelist):
2622 debug("\n----------------------\n")
2623 debug("len(facelits): %d\n" % len(facelist))
2626 pSign = sign(P.normal[2])
2628 # We can discard faces parallel to the view vector
2629 #if P.normal[2] == 0:
2630 # facelist.remove(P)
2636 for Q in facelist[1:]:
2638 debug("P.smooth: " + str(P.smooth) + "\n")
2639 debug("Q.smooth: " + str(Q.smooth) + "\n")
2642 qSign = sign(Q.normal[2])
2643 # TODO: check also if Q is parallel??
2645 # Test 0: We need to test only those Qs whose furthest vertex
2646 # is closer to the observer than the closest vertex of P.
2648 zP = [v.co[2] for v in P.v]
2649 zQ = [v.co[2] for v in Q.v]
2650 notZOverlap = min(zP) > max(zQ) + EPS
2654 debug("NOT Z OVERLAP!\n")
2656 # If Q is not marked then we can safely print P
2659 debug("met a marked face\n")
2663 # Test 1: X extent overlapping
2664 xP = [v.co[0] for v in P.v]
2665 xQ = [v.co[0] for v in Q.v]
2666 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2667 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2671 debug("NOT X OVERLAP!\n")
2675 # Test 2: Y extent Overlapping
2676 yP = [v.co[1] for v in P.v]
2677 yQ = [v.co[1] for v in Q.v]
2678 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2679 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2683 debug("NOT Y OVERLAP!\n")
2687 # Test 3: P vertices are all behind the plane of Q
2690 d = qSign * HSR.Distance(Vector(Pi), Q)
2693 pVerticesBehindPlaneQ = (n == len(P))
2695 if pVerticesBehindPlaneQ:
2697 debug("P BEHIND Q!\n")
2701 # Test 4: Q vertices in front of the plane of P
2704 d = pSign * HSR.Distance(Vector(Qi), P)
2707 qVerticesInFrontPlaneP = (n == len(Q))
2709 if qVerticesInFrontPlaneP:
2711 debug("Q IN FRONT OF P!\n")
2715 # Test 5: Check if projections of polygons effectively overlap,
2716 # in previous tests we checked only bounding boxes.
2718 #if not projectionsOverlap(P, Q):
2719 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2721 debug("Projections do not overlap!\n")
2724 # We still can't say if P obscures Q.
2726 # But if Q is marked we do a face-split trying to resolve a
2727 # difficulty (maybe a visibility cycle).
2730 debug("Possibly a cycle detected!\n")
2731 debug("Split here!!\n")
2733 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2737 # The question now is: Does Q obscure P?
2740 # Test 3bis: Q vertices are all behind the plane of P
2743 d = pSign * HSR.Distance(Vector(Qi), P)
2746 qVerticesBehindPlaneP = (n == len(Q))
2748 if qVerticesBehindPlaneP:
2749 debug("\nTest 3bis\n")
2750 debug("Q BEHIND P!\n")
2753 # Test 4bis: P vertices in front of the plane of Q
2756 d = qSign * HSR.Distance(Vector(Pi), Q)
2759 pVerticesInFrontPlaneQ = (n == len(P))
2761 if pVerticesInFrontPlaneQ:
2762 debug("\nTest 4bis\n")
2763 debug("P IN FRONT OF Q!\n")
2766 # We don't even know if Q does obscure P, so they should
2767 # intersect each other, split one of them in two parts.
2768 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2769 debug("\nSimple Intersection?\n")
2770 debug("Test 3bis or 4bis failed\n")
2771 debug("Split here!!2\n")
2773 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2778 facelist.insert(0, Q)
2781 debug("Q marked!\n")
2785 if split_done == 0 and face_marked == 0:
2788 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2792 if len(facelist) == 870:
2793 dumpfaces([P, Q], "loopdebug.svg")
2796 #if facelist == None:
2798 # print [v.co for v in P]
2799 # print [v.co for v in Q]
2802 # end of while len(facelist)
2805 nmesh.faces = maplist
2806 #for f in nmesh.faces:
2812 def _doHiddenSurfaceRemoval(self, mesh):
2813 """Do HSR for the given mesh.
2815 if len(mesh.faces) == 0:
2818 if config.polygons['HSR'] == 'PAINTER':
2819 print "\nUsing the Painter algorithm for HSR."
2820 self.__simpleDepthSort(mesh)
2822 elif config.polygons['HSR'] == 'NEWELL':
2823 print "\nUsing the Newell's algorithm for HSR."
2824 self.__newellDepthSort(mesh)
2827 def _doEdgesStyle(self, mesh, edgestyleSelect):
2828 """Process Mesh Edges accroding to a given selection style.
2830 Examples of algorithms:
2833 given an edge if its adjacent faces have the same normal (that is
2834 they are complanar), than deselect it.
2837 given an edge if one its adjacent faces is frontfacing and the
2838 other is backfacing, than select it, else deselect.
2841 Mesh.Mode(Mesh.SelectModes['EDGE'])
2843 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2845 for i,edge_faces in enumerate(edge_cache):
2846 mesh.edges[i].sel = 0
2847 if edgestyleSelect(edge_faces):
2848 mesh.edges[i].sel = 1
2851 for e in mesh.edges:
2854 if edgestyleSelect(e, mesh):
2860 # ---------------------------------------------------------------------
2862 ## GUI Class and Main Program
2864 # ---------------------------------------------------------------------
2867 from Blender import BGL, Draw
2868 from Blender.BGL import *
2874 # Output Format menu
2875 output_format = config.output['FORMAT']
2876 default_value = outputWriters.keys().index(output_format)+1
2877 GUI.outFormatMenu = Draw.Create(default_value)
2878 GUI.evtOutFormatMenu = 0
2880 # Animation toggle button
2881 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2882 GUI.evtAnimToggle = 1
2884 # Join Objects toggle button
2885 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2886 GUI.evtJoinObjsToggle = 2
2888 # Render filled polygons
2889 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2891 # Shading Style menu
2892 shading_style = config.polygons['SHADING']
2893 default_value = shadingStyles.keys().index(shading_style)+1
2894 GUI.shadingStyleMenu = Draw.Create(default_value)
2895 GUI.evtShadingStyleMenu = 21
2897 GUI.evtPolygonsToggle = 3
2898 # We hide the config.polygons['EXPANSION_TRICK'], for now
2900 # Render polygon edges
2901 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2902 GUI.evtShowEdgesToggle = 4
2904 # Render hidden edges
2905 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2906 GUI.evtShowHiddenEdgesToggle = 5
2909 edge_style = config.edges['STYLE']
2910 default_value = edgeStyles.keys().index(edge_style)+1
2911 GUI.edgeStyleMenu = Draw.Create(default_value)
2912 GUI.evtEdgeStyleMenu = 6
2915 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2916 GUI.evtEdgeWidthSlider = 7
2919 c = config.edges['COLOR']
2920 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2921 GUI.evtEdgeColorPicker = 71
2924 GUI.evtRenderButton = 8
2927 GUI.evtExitButton = 9
2931 # initialize static members
2934 glClear(GL_COLOR_BUFFER_BIT)
2935 glColor3f(0.0, 0.0, 0.0)
2936 glRasterPos2i(10, 350)
2937 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2939 glRasterPos2i(10, 335)
2940 Draw.Text("Press Q or ESC to quit.")
2942 # Build the output format menu
2943 glRasterPos2i(10, 310)
2944 Draw.Text("Select the output Format:")
2945 outMenuStruct = "Output Format %t"
2946 for t in outputWriters.keys():
2947 outMenuStruct = outMenuStruct + "|%s" % t
2948 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2949 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2952 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2953 10, 260, 160, 18, GUI.animToggle.val,
2954 "Toggle rendering of animations")
2956 # Join Objects toggle
2957 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2958 10, 235, 160, 18, GUI.joinObjsToggle.val,
2959 "Join objects in the rendered file")
2962 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2964 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2967 glRasterPos2i(200, 310)
2968 Draw.Text("Rendering Style:")
2971 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
2972 200, 285, 160, 18, GUI.polygonsToggle.val,
2973 "Render filled polygons")
2975 if GUI.polygonsToggle.val == 1:
2977 # Polygon Shading Style
2978 shadingStyleMenuStruct = "Shading Style %t"
2979 for t in shadingStyles.keys():
2980 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
2981 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
2982 200, 260, 160, 18, GUI.shadingStyleMenu.val,
2983 "Choose the shading style")
2987 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
2988 200, 235, 160, 18, GUI.showEdgesToggle.val,
2989 "Render polygon edges")
2991 if GUI.showEdgesToggle.val == 1:
2994 edgeStyleMenuStruct = "Edge Style %t"
2995 for t in edgeStyles.keys():
2996 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
2997 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
2998 200, 210, 160, 18, GUI.edgeStyleMenu.val,
2999 "Choose the edge style")
3002 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
3003 200, 185, 140, 18, GUI.edgeWidthSlider.val,
3004 0.0, 10.0, 0, "Change Edge Width")
3007 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
3008 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
3011 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
3012 GUI.evtShowHiddenEdgesToggle,
3013 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
3014 "Render hidden edges as dashed lines")
3016 glRasterPos2i(10, 160)
3017 Draw.Text("%s (c) 2006" % __author__)
3019 def event(evt, val):
3021 if evt == Draw.ESCKEY or evt == Draw.QKEY:
3028 def button_event(evt):
3030 if evt == GUI.evtExitButton:
3033 elif evt == GUI.evtOutFormatMenu:
3034 i = GUI.outFormatMenu.val - 1
3035 config.output['FORMAT']= outputWriters.keys()[i]
3036 # Set the new output file
3038 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3040 elif evt == GUI.evtAnimToggle:
3041 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3043 elif evt == GUI.evtJoinObjsToggle:
3044 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3046 elif evt == GUI.evtPolygonsToggle:
3047 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3049 elif evt == GUI.evtShadingStyleMenu:
3050 i = GUI.shadingStyleMenu.val - 1
3051 config.polygons['SHADING'] = shadingStyles.keys()[i]
3053 elif evt == GUI.evtShowEdgesToggle:
3054 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3056 elif evt == GUI.evtShowHiddenEdgesToggle:
3057 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3059 elif evt == GUI.evtEdgeStyleMenu:
3060 i = GUI.edgeStyleMenu.val - 1
3061 config.edges['STYLE'] = edgeStyles.keys()[i]
3063 elif evt == GUI.evtEdgeWidthSlider:
3064 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3066 elif evt == GUI.evtEdgeColorPicker:
3067 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3069 elif evt == GUI.evtRenderButton:
3070 label = "Save %s" % config.output['FORMAT']
3071 # Show the File Selector
3073 Blender.Window.FileSelector(vectorize, label, outputfile)
3076 print "Event: %d not handled!" % evt
3083 from pprint import pprint
3085 pprint(config.output)
3086 pprint(config.polygons)
3087 pprint(config.edges)
3089 _init = staticmethod(_init)
3090 draw = staticmethod(draw)
3091 event = staticmethod(event)
3092 button_event = staticmethod(button_event)
3093 conf_debug = staticmethod(conf_debug)
3095 # A wrapper function for the vectorizing process
3096 def vectorize(filename):
3097 """The vectorizing process is as follows:
3099 - Instanciate the writer and the renderer
3104 print "\nERROR: invalid file name!"
3107 from Blender import Window
3108 editmode = Window.EditMode()
3109 if editmode: Window.EditMode(0)
3111 actualWriter = outputWriters[config.output['FORMAT']]
3112 writer = actualWriter(filename)
3114 renderer = Renderer()
3115 renderer.doRendering(writer, config.output['ANIMATION'])
3117 if editmode: Window.EditMode(1)
3122 if __name__ == "__main__":
3127 basename = Blender.sys.basename(Blender.Get('filename'))
3129 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3131 if Blender.mode == 'background':
3132 progress = ConsoleProgressIndicator()
3133 vectorize(outputfile)
3135 progress = GraphicalProgressIndicator()
3136 Draw.Register(GUI.draw, GUI.event, GUI.button_event)