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)
88 # ---------------------------------------------------------------------
91 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
92 from Blender.Mathutils import *
99 # We use a global progress Indicator Object
103 # Some global settings
107 polygons['SHOW'] = True
108 polygons['SHADING'] = 'FLAT' # FLAT or TOON
109 polygons['HSR'] = 'NEWELL' # PAINTER or NEWELL
110 # Hidden to the user for now
111 polygons['EXPANSION_TRICK'] = True
113 polygons['TOON_LEVELS'] = 2
116 edges['SHOW'] = False
117 edges['SHOW_HIDDEN'] = False
118 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
120 edges['COLOR'] = [0, 0, 0]
123 output['FORMAT'] = 'SVG'
124 output['ANIMATION'] = False
125 output['JOIN_OBJECTS'] = True
131 def dumpfaces(flist, filename):
132 """Dump a single face to a file.
143 writerobj = SVGVectorWriter(filename)
146 writerobj._printPolygons(m)
152 sys.stderr.write(msg)
155 return (abs(v1[0]-v2[0]) < EPS and
156 abs(v1[1]-v2[1]) < EPS )
157 by_furthest_z = (lambda f1, f2:
158 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
173 # ---------------------------------------------------------------------
177 # ---------------------------------------------------------------------
183 """A utility class for HSR processing.
186 def is_nonplanar_quad(face):
187 """Determine if a quad is non-planar.
189 From: http://mathworld.wolfram.com/Coplanar.html
191 Geometric objects lying in a common plane are said to be coplanar.
192 Three noncollinear points determine a plane and so are trivially coplanar.
193 Four points are coplanar iff the volume of the tetrahedron defined by them is
199 | x_4 y_4 z_4 1 | == 0
201 Coplanarity is equivalent to the statement that the pair of lines
202 determined by the four points are not skew, and can be equivalently stated
203 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
205 An arbitrary number of n points x_1, ..., x_n can be tested for
206 coplanarity by finding the point-plane distances of the points
207 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
208 and checking if they are all zero.
209 If so, the points are all coplanar.
211 We here check only for 4-point complanarity.
217 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
221 # three points must be complanar
224 x1 = Vector(face[0].co)
225 x2 = Vector(face[1].co)
226 x3 = Vector(face[2].co)
227 x4 = Vector(face[3].co)
229 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
235 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
237 def pointInPolygon(poly, v):
240 pointInPolygon = staticmethod(pointInPolygon)
242 def edgeIntersection(s1, s2, do_perturbate=False):
244 (x1, y1) = s1[0].co[0], s1[0].co[1]
245 (x2, y2) = s1[1].co[0], s1[1].co[1]
247 (x3, y3) = s2[0].co[0], s2[0].co[1]
248 (x4, y4) = s2[1].co[0], s2[1].co[1]
256 # calculate delta values (vector components)
265 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
266 if C == 0: #/* parallel */
269 dx3 = x1 - x3 # /* combined origin offset vector */
272 a1 = (dy3 * dx2 - dx3 * dy2) / C;
273 a2 = (dy3 * dx1 - dx3 * dy1) / C;
275 # check for degeneracies
277 #print_debug(str(a1)+"\n")
278 #print_debug(str(a2)+"\n\n")
280 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
281 # Intersection on boundaries, we consider the point external?
284 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
290 return (NMesh.Vert(x, y, z), a1, a2)
293 # lines have intersections but not those segments
296 edgeIntersection = staticmethod(edgeIntersection)
298 def isVertInside(self, v):
302 # Create point at infinity
303 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
305 for i in range(len(self.v)):
306 s1 = (point_at_infinity, v)
307 s2 = (self.v[i-1], self.v[i])
309 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
312 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
316 if winding_number % 2 == 0 :
323 isVertInside = staticmethod(isVertInside)
327 return ((b[0] - a[0]) * (c[1] - a[1]) -
328 (b[1] - a[1]) * (c[0] - a[0]) )
330 det = staticmethod(det)
332 def pointInPolygon(q, P):
335 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
339 for i in range(len(P.v)):
342 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
343 if det(p0.co, p1.co, q.co) == 0 :
346 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
351 pointInPolygon = staticmethod(pointInPolygon)
353 def projectionsOverlap(f1, f2):
354 """ If you have nonconvex, but still simple polygons, an acceptable method
355 is to iterate over all vertices and perform the Point-in-polygon test[1].
356 The advantage of this method is that you can compute the exact
357 intersection point and collision normal that you will need to simulate
358 collision. When you have the point that lies inside the other polygon, you
359 just iterate over all edges of the second polygon again and look for edge
360 intersections. Note that this method detects collsion when it already
361 happens. This algorithm is fast enough to perform it hundreds of times per
364 for i in range(len(f1.v)):
367 # If a point of f1 in inside f2, there is an overlap!
369 #if HSR.isVertInside(f2, v1):
370 if HSR.pointInPolygon(v1, f2):
373 # If not the polygon can be ovelap as well, so we check for
374 # intersection between an edge of f1 and all the edges of f2
378 for j in range(len(f2.v)):
385 intrs = HSR.edgeIntersection(e1, e2)
387 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
388 # str(v2.co) + " " + str(v3.co) )
389 #print_debug("\nIntersection\n")
395 projectionsOverlap = staticmethod(projectionsOverlap)
397 def midpoint(p1, p2):
398 """Return the midpoint of two vertices.
400 m = MidpointVecs(Vector(p1), Vector(p2))
401 mv = NMesh.Vert(m[0], m[1], m[2])
405 midpoint = staticmethod(midpoint)
407 def facesplit(P, Q, facelist, nmesh):
408 """Split P or Q according to the strategy illustrated in the Newell's
412 by_furthest_z = (lambda f1, f2:
413 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
416 # Choose if split P on Q plane or vice-versa
420 d = HSR.Distance(Vector(Pi), Q)
423 pIntersectQ = (n != len(P))
427 d = HSR.Distance(Vector(Qi), P)
430 qIntersectP = (n != len(Q))
434 # 1. If parts of P lie in both half-spaces of Q
435 # then splice P in two with the plane of Q
441 newfaces = HSR.splitOn(plane, f)
443 # 2. Else if parts of Q lie in both half-space of P
444 # then splice Q in two with the plane of P
445 if qIntersectP and newfaces == None:
450 newfaces = HSR.splitOn(plane, f)
453 # 3. Else slice P in half through the mid-point of
454 # the longest pair of opposite sides
457 print "We ignore P..."
464 # v1 = midpoint(f[0], f[1])
465 # v2 = midpoint(f[1], f[2])
467 # v1 = midpoint(f[0], f[1])
468 # v2 = midpoint(f[2], f[3])
469 #vec3 = (Vector(v2)+10*Vector(f.normal))
471 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
473 #plane = NMesh.Face([v1, v2, v3])
475 #newfaces = splitOn(plane, f)
479 print "Big FAT problem, we weren't able to split POLYGONS!"
485 # if v not in plane and v in nmesh.verts:
486 # nmesh.verts.remove(v)
491 nf.col = [f.col[0]] * len(nf.v)
496 nmesh.verts.append(v)
497 # insert pieces in the list
502 # and resort the faces
503 facelist.sort(by_furthest_z)
504 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
507 #print [ f.smooth for f in facelist ]
511 facesplit = staticmethod(facesplit)
513 def isOnSegment(v1, v2, p, extremes_internal=False):
514 """Check if point p is in segment v1v2.
520 # Should we consider extreme points as internal ?
522 # if p == v1 or p == v2:
523 if l1 < EPS or l2 < EPS:
524 return extremes_internal
528 # if the sum of l1 and l2 is circa l, then the point is on segment,
529 if abs(l - (l1+l2)) < EPS:
534 isOnSegment = staticmethod(isOnSegment)
536 def Distance(point, face):
537 """ Calculate the distance between a point and a face.
539 An alternative but more expensive method can be:
541 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
542 Vector(face.no), Vector(point), 0)
544 d = Vector(ip - point).length
546 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
550 plNormal = Vector(face.no)
551 plVert0 = Vector(face.v[0])
553 d = (plVert0 * plNormal) - (p * plNormal)
555 #d = plNormal * (plVert0 - p)
557 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
561 Distance = staticmethod(Distance)
565 # make one or two new faces based on a list of vertex-indices
594 makeFaces = staticmethod(makeFaces)
596 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
597 """Split P using the plane of Q.
598 Logic taken from the knife.py python script
601 # Check if P and Q are parallel
602 u = CrossVecs(Vector(Q.no),Vector(P.no))
608 print "PARALLEL planes!!"
612 # The final aim is to find the intersection line between P
613 # and the plane of Q, and split P along this line
617 # Calculate point-plane Distance between vertices of P and plane Q
619 for i in range(0, nP):
620 d.append(HSR.Distance(P.v[i], Q))
633 #print "d0:", d0, "d1:", d1
635 # if the vertex lies in the cutplane
637 #print "d1 On cutplane"
638 posVertList.append(V1)
639 negVertList.append(V1)
641 # if the previous vertex lies in cutplane
643 #print "d0 on Cutplane"
645 #print "d1 on positive Halfspace"
646 posVertList.append(V1)
648 #print "d1 on negative Halfspace"
649 negVertList.append(V1)
651 # if they are on the same side of the plane
653 #print "On the same half-space"
655 #print "d1 on positive Halfspace"
656 posVertList.append(V1)
658 #print "d1 on negative Halfspace"
659 negVertList.append(V1)
661 # the vertices are not on the same side of the plane, so we have an intersection
663 #print "Intersection"
665 e = Vector(V0), Vector(V1)
666 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
668 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
673 #print "Intersection", inters
675 nv = NMesh.Vert(inters[0], inters[1], inters[2])
676 newVertList.append(nv)
678 posVertList.append(nv)
679 negVertList.append(nv)
682 posVertList.append(V1)
684 negVertList.append(V1)
688 posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
689 negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
692 # If vertex are all on the same half-space, return
693 #if len(posVertList) < 3:
694 # print "Problem, we created a face with less that 3 vertices??"
696 #if len(negVertList) < 3:
697 # print "Problem, we created a face with less that 3 vertices??"
700 if len(posVertList) < 3 or len(negVertList) < 3:
701 #print "RETURN NONE, SURE???"
704 if not return_positive_faces:
706 if not return_negative_faces:
709 newfaces = HSR.addNewFaces(posVertList, negVertList)
713 splitOn = staticmethod(splitOn)
715 def addNewFaces(posVertList, negVertList):
716 # Create new faces resulting from the split
718 if len(posVertList) or len(negVertList):
720 #newfaces = [posVertList] + [negVertList]
721 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
722 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
726 outfaces += HSR.makeFaces(nf)
731 addNewFaces = staticmethod(addNewFaces)
734 # ---------------------------------------------------------------------
736 ## Mesh Utility class
738 # ---------------------------------------------------------------------
742 def buildEdgeFaceUsersCache(me):
744 Takes a mesh and returns a list aligned with the meshes edges.
745 Each item is a list of the faces that use the edge
746 would be the equiv for having ed.face_users as a property
748 Taken from .blender/scripts/bpymodules/BPyMesh.py,
749 thanks to ideasman_42.
752 def sorted_edge_indicies(ed):
760 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
762 fvi= [v.index for v in f.v]# face vert idx's
763 for i in xrange(len(f)):
770 face_edges_dict[i1,i2][1].append(f)
772 face_edges= [None] * len(me.edges)
773 for ed_index, ed_faces in face_edges_dict.itervalues():
774 face_edges[ed_index]= ed_faces
778 def isMeshEdge(adjacent_faces):
781 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
782 Note: if the edge has no adjacent faces we want to show it as well,
783 useful for "edge only" portion of objects.
786 if len(adjacent_faces) == 0:
789 selected_faces = [f for f in adjacent_faces if f.sel]
791 if len(selected_faces) != 0:
796 def isSilhouetteEdge(adjacent_faces):
797 """Silhuette selection rule.
799 An edge is a silhuette edge if it is shared by two faces with
800 different selection status or if it is a boundary edge of a selected
804 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
805 (len(adjacent_faces) == 2 and
806 adjacent_faces[0].sel != adjacent_faces[1].sel)
812 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
813 isMeshEdge = staticmethod(isMeshEdge)
814 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
817 # ---------------------------------------------------------------------
819 ## Shading Utility class
821 # ---------------------------------------------------------------------
827 def toonShadingMapSetup():
828 levels = config.polygons['TOON_LEVELS']
830 texels = 2*levels - 1
831 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
837 shademap = ShadingUtils.shademap
840 shademap = ShadingUtils.toonShadingMapSetup()
843 for i in xrange(0, len(shademap)-1):
844 pivot = (shademap[i]+shademap[i+1])/2.0
849 if v < shademap[i+1]:
854 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
855 toonShading = staticmethod(toonShading)
858 # ---------------------------------------------------------------------
860 ## Projections classes
862 # ---------------------------------------------------------------------
865 """Calculate the projection of an object given the camera.
867 A projector is useful to so some per-object transformation to obtain the
868 projection of an object given the camera.
870 The main method is #doProjection# see the method description for the
874 def __init__(self, cameraObj, canvasRatio):
875 """Calculate the projection matrix.
877 The projection matrix depends, in this case, on the camera settings.
878 TAKE CARE: This projector expects vertices in World Coordinates!
881 camera = cameraObj.getData()
883 aspect = float(canvasRatio[0])/float(canvasRatio[1])
884 near = camera.clipStart
887 scale = float(camera.scale)
889 fovy = atan(0.5/aspect/(camera.lens/32))
890 fovy = fovy * 360.0/pi
893 if Blender.Get('version') < 243:
900 # What projection do we want?
901 if camera.type == camPersp:
902 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
903 elif camera.type == camOrtho:
904 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
907 # View transformation
908 cam = Matrix(cameraObj.getInverseMatrix())
913 self.projectionMatrix = mP
919 def doProjection(self, v):
920 """Project the point on the view plane.
922 Given a vertex calculate the projection using the current projection
926 # Note that we have to work on the vertex using homogeneous coordinates
927 # From blender 2.42+ we don't need to resize the vector to be 4d
928 # when applying a 4x4 matrix, but we do that anyway since we need the
929 # 4th coordinate later
930 p = self.projectionMatrix * Vector(v).resize4D()
932 # Perspective division
949 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
950 """Return a perspective projection matrix.
953 top = near * tan(fovy * pi / 360.0)
957 x = (2.0 * near) / (right-left)
958 y = (2.0 * near) / (top-bottom)
959 a = (right+left) / (right-left)
960 b = (top+bottom) / (top - bottom)
961 c = - ((far+near) / (far-near))
962 d = - ((2*far*near)/(far-near))
968 [0.0, 0.0, -1.0, 0.0])
972 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
973 """Return an orthogonal projection matrix.
976 # The 11 in the formula was found emiprically
977 top = near * tan(fovy * pi / 360.0) * (scale * 11)
979 left = bottom * aspect
984 tx = -((right+left)/rl)
985 ty = -((top+bottom)/tb)
989 [2.0/rl, 0.0, 0.0, tx],
990 [0.0, 2.0/tb, 0.0, ty],
991 [0.0, 0.0, 2.0/fn, tz],
992 [0.0, 0.0, 0.0, 1.0])
997 # ---------------------------------------------------------------------
999 ## Progress Indicator
1001 # ---------------------------------------------------------------------
1004 """A model for a progress indicator.
1006 Do the progress calculation calculation and
1007 the view independent stuff of a progress indicator.
1009 def __init__(self, steps=0):
1015 def setSteps(self, steps):
1016 """Set the number of steps of the activity wich we want to track.
1023 def setName(self, name):
1024 """Set the name of the activity wich we want to track.
1031 def getProgress(self):
1032 return self.progress
1039 """Update the model, call this method when one step is completed.
1041 if self.progress == 100:
1045 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1046 self.progress = int(self.progress)
1051 class ProgressIndicator:
1052 """An abstraction of a View for the Progress Model
1056 # Use a refresh rate so we do not show the progress at
1057 # every update, but every 'self.refresh_rate' times.
1058 self.refresh_rate = 10
1059 self.shows_counter = 0
1063 self.progressModel = None
1065 def setQuiet(self, value):
1068 def setActivity(self, name, steps):
1069 """Initialize the Model.
1071 In a future version (with subactivities-progress support) this method
1072 could only set the current activity.
1074 self.progressModel = Progress()
1075 self.progressModel.setName(name)
1076 self.progressModel.setSteps(steps)
1078 def getActivity(self):
1079 return self.progressModel
1082 """Update the model and show the actual progress.
1084 assert(self.progressModel)
1086 if self.progressModel.update():
1090 self.show(self.progressModel.getProgress(),
1091 self.progressModel.getName())
1093 # We return always True here so we can call the update() method also
1094 # from lambda funcs (putting the call in logical AND with other ops)
1097 def show(self, progress, name=""):
1098 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1099 if self.shows_counter != 0:
1103 self.shows_counter = -1
1106 class ConsoleProgressIndicator(ProgressIndicator):
1107 """Show a progress bar on stderr, a la wget.
1110 ProgressIndicator.__init__(self)
1112 self.swirl_chars = ["-", "\\", "|", "/"]
1113 self.swirl_count = -1
1115 def show(self, progress, name):
1116 ProgressIndicator.show(self, progress, name)
1119 bar_progress = int( (progress/100.0) * bar_length )
1120 bar = ("=" * bar_progress).ljust(bar_length)
1122 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1123 swirl_char = self.swirl_chars[self.swirl_count]
1125 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1127 sys.stderr.write(progress_bar+"\r")
1129 sys.stderr.write("\n")
1132 class GraphicalProgressIndicator(ProgressIndicator):
1133 """Interface to the Blender.Window.DrawProgressBar() method.
1136 ProgressIndicator.__init__(self)
1138 #self.swirl_chars = ["-", "\\", "|", "/"]
1139 # We have to use letters with the same width, for now!
1140 # Blender progress bar considers the font widths when
1141 # calculating the progress bar width.
1142 self.swirl_chars = ["\\", "/"]
1143 self.swirl_count = -1
1145 def show(self, progress, name):
1146 ProgressIndicator.show(self, progress)
1148 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1149 swirl_char = self.swirl_chars[self.swirl_count]
1151 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1153 # Finally draw the Progress Bar
1154 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1155 Window.DrawProgressBar(progress/100.0, progress_text)
1158 Window.DrawProgressBar(1, progress_text)
1159 Window.WaitCursor(0)
1163 # ---------------------------------------------------------------------
1165 ## 2D Object representation class
1167 # ---------------------------------------------------------------------
1169 # TODO: a class to represent the needed properties of a 2D vector image
1170 # For now just using a [N]Mesh structure.
1173 # ---------------------------------------------------------------------
1175 ## Vector Drawing Classes
1177 # ---------------------------------------------------------------------
1183 A class for printing output in a vectorial format.
1185 Given a 2D representation of the 3D scene the class is responsible to
1186 write it is a vector format.
1188 Every subclasses of VectorWriter must have at last the following public
1192 - printCanvas(self, scene,
1193 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1196 def __init__(self, fileName):
1197 """Set the output file name and other properties"""
1199 self.outputFileName = fileName
1201 context = Scene.GetCurrent().getRenderingContext()
1202 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1204 self.fps = context.fps
1208 self.animation = False
1215 def open(self, startFrame=1, endFrame=1):
1216 if startFrame != endFrame:
1217 self.startFrame = startFrame
1218 self.endFrame = endFrame
1219 self.animation = True
1221 print "Outputting to: ", self.outputFileName
1228 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1229 showHiddenEdges=False):
1230 """This is the interface for the needed printing routine.
1237 class SVGVectorWriter(VectorWriter):
1238 """A concrete class for writing SVG output.
1241 def __init__(self, fileName):
1242 """Simply call the parent Contructor.
1244 VectorWriter.__init__(self, fileName)
1253 def open(self, startFrame=1, endFrame=1):
1254 """Do some initialization operations.
1256 VectorWriter.open(self, startFrame, endFrame)
1258 self.file = open(self.outputFileName, "w")
1263 """Do some finalization operation.
1270 # remember to call the close method of the parent as last
1271 VectorWriter.close(self)
1274 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1275 showHiddenEdges=False):
1276 """Convert the scene representation to SVG.
1279 Objects = scene.getChildren()
1281 context = scene.getRenderingContext()
1282 framenumber = context.currentFrame()
1285 framestyle = "display:none"
1287 framestyle = "display:block"
1289 # Assign an id to this group so we can set properties on it using DOM
1290 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1291 (framenumber, framestyle) )
1296 if(obj.getType() != 'Mesh'):
1299 self.file.write("<g id=\"%s\">\n" % obj.getName())
1301 mesh = obj.getData(mesh=1)
1304 self._printPolygons(mesh)
1307 self._printEdges(mesh, showHiddenEdges)
1309 self.file.write("</g>\n")
1311 self.file.write("</g>\n")
1318 def _calcCanvasCoord(self, v):
1319 """Convert vertex in scene coordinates to canvas coordinates.
1322 pt = Vector([0, 0, 0])
1324 mW = float(self.canvasSize[0])/2.0
1325 mH = float(self.canvasSize[1])/2.0
1327 # rescale to canvas size
1328 pt[0] = v.co[0]*mW + mW
1329 pt[1] = v.co[1]*mH + mH
1332 # For now we want (0,0) in the top-left corner of the canvas.
1333 # Mirror and translate along y
1335 pt[1] += self.canvasSize[1]
1339 def _printHeader(self):
1340 """Print SVG header."""
1342 self.file.write("<?xml version=\"1.0\"?>\n")
1343 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1344 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1345 self.file.write("<svg version=\"1.0\"\n")
1346 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1347 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1351 delay = 1000/self.fps
1353 self.file.write("""\n<script type="text/javascript"><![CDATA[
1354 globalStartFrame=%d;
1357 timerID = setInterval("NextFrame()", %d);
1358 globalFrameCounter=%d;
1359 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1361 self.file.write("""\n
1362 function NextFrame()
1364 currentElement = document.getElementById('frame'+globalFrameCounter)
1365 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1367 if (!currentElement)
1372 if (globalFrameCounter > globalEndFrame)
1374 clearInterval(timerID)
1380 previousElement.style.display="none";
1382 currentElement.style.display="block";
1383 globalFrameCounter++;
1389 def _printFooter(self):
1390 """Print the SVG footer."""
1392 self.file.write("\n</svg>\n")
1394 def _printPolygons(self, mesh):
1395 """Print the selected (visible) polygons.
1398 if len(mesh.faces) == 0:
1401 self.file.write("<g>\n")
1403 for face in mesh.faces:
1407 self.file.write("<path d=\"")
1409 #p = self._calcCanvasCoord(face.verts[0])
1410 p = self._calcCanvasCoord(face.v[0])
1411 self.file.write("M %g,%g L " % (p[0], p[1]))
1413 for v in face.v[1:]:
1414 p = self._calcCanvasCoord(v)
1415 self.file.write("%g,%g " % (p[0], p[1]))
1417 # get rid of the last blank space, just cosmetics here.
1418 self.file.seek(-1, 1)
1419 self.file.write(" z\"\n")
1421 # take as face color the first vertex color
1424 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1426 color = [255, 255, 255, 255]
1428 # Convert the color to the #RRGGBB form
1429 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1431 # Handle transparent polygons
1434 opacity = float(color[3])/255.0
1435 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1436 #opacity_string = "opacity: %g;" % (opacity)
1438 self.file.write("\tstyle=\"fill:" + str_col + ";")
1439 self.file.write(opacity_string)
1441 # use the stroke property to alleviate the "adjacent edges" problem,
1442 # we simulate polygon expansion using borders,
1443 # see http://www.antigrain.com/svg/index.html for more info
1446 # EXPANSION TRICK is not that useful where there is transparency
1447 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1448 # str_col = "#000000" # For debug
1449 self.file.write(" stroke:%s;\n" % str_col)
1450 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1451 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1453 self.file.write("\"/>\n")
1455 self.file.write("</g>\n")
1457 def _printEdges(self, mesh, showHiddenEdges=False):
1458 """Print the wireframe using mesh edges.
1461 stroke_width = config.edges['WIDTH']
1462 stroke_col = config.edges['COLOR']
1464 self.file.write("<g>\n")
1466 for e in mesh.edges:
1468 hidden_stroke_style = ""
1471 if showHiddenEdges == False:
1474 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1476 p1 = self._calcCanvasCoord(e.v1)
1477 p2 = self._calcCanvasCoord(e.v2)
1479 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1480 % ( p1[0], p1[1], p2[0], p2[1] ) )
1481 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1482 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1483 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1484 self.file.write(hidden_stroke_style)
1485 self.file.write("\"/>\n")
1487 self.file.write("</g>\n")
1496 SWFSupported = False
1498 class SWFVectorWriter(VectorWriter):
1499 """A concrete class for writing SWF output.
1502 def __init__(self, fileName):
1503 """Simply call the parent Contructor.
1505 VectorWriter.__init__(self, fileName)
1515 def open(self, startFrame=1, endFrame=1):
1516 """Do some initialization operations.
1518 VectorWriter.open(self, startFrame, endFrame)
1519 self.movie = SWFMovie()
1520 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1522 self.movie.setRate(self.fps)
1523 numframes = endFrame - startFrame + 1
1524 self.movie.setFrames(numframes)
1527 """Do some finalization operation.
1529 self.movie.save(self.outputFileName)
1531 # remember to call the close method of the parent
1532 VectorWriter.close(self)
1534 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1535 showHiddenEdges=False):
1536 """Convert the scene representation to SVG.
1538 context = scene.getRenderingContext()
1539 framenumber = context.currentFrame()
1541 Objects = scene.getChildren()
1544 self.movie.remove(self.sprite)
1546 sprite = SWFSprite()
1550 if(obj.getType() != 'Mesh'):
1553 mesh = obj.getData(mesh=1)
1556 self._printPolygons(mesh, sprite)
1559 self._printEdges(mesh, sprite, showHiddenEdges)
1562 i = self.movie.add(sprite)
1563 # Remove the instance the next time
1566 self.movie.nextFrame()
1573 def _calcCanvasCoord(self, v):
1574 """Convert vertex in scene coordinates to canvas coordinates.
1577 pt = Vector([0, 0, 0])
1579 mW = float(self.canvasSize[0])/2.0
1580 mH = float(self.canvasSize[1])/2.0
1582 # rescale to canvas size
1583 pt[0] = v.co[0]*mW + mW
1584 pt[1] = v.co[1]*mH + mH
1587 # For now we want (0,0) in the top-left corner of the canvas.
1588 # Mirror and translate along y
1590 pt[1] += self.canvasSize[1]
1594 def _printPolygons(self, mesh, sprite):
1595 """Print the selected (visible) polygons.
1598 if len(mesh.faces) == 0:
1601 for face in mesh.faces:
1607 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1609 color = [255, 255, 255, 255]
1612 f = s.addFill(color[0], color[1], color[2], color[3])
1615 # The starting point of the shape
1616 p0 = self._calcCanvasCoord(face.verts[0])
1617 s.movePenTo(p0[0], p0[1])
1619 for v in face.verts[1:]:
1620 p = self._calcCanvasCoord(v)
1621 s.drawLineTo(p[0], p[1])
1624 s.drawLineTo(p0[0], p0[1])
1630 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1631 """Print the wireframe using mesh edges.
1634 stroke_width = config.edges['WIDTH']
1635 stroke_col = config.edges['COLOR']
1639 for e in mesh.edges:
1641 # Next, we set the line width and color for our shape.
1642 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1646 if showHiddenEdges == False:
1649 # SWF does not support dashed lines natively, so -for now-
1650 # draw hidden lines thinner and half-trasparent
1651 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1654 p1 = self._calcCanvasCoord(e.v1)
1655 p2 = self._calcCanvasCoord(e.v2)
1657 s.movePenTo(p1[0], p1[1])
1658 s.drawLineTo(p2[0], p2[1])
1667 from reportlab.pdfgen import canvas
1670 PDFSupported = False
1672 class PDFVectorWriter(VectorWriter):
1673 """A concrete class for writing PDF output.
1676 def __init__(self, fileName):
1677 """Simply call the parent Contructor.
1679 VectorWriter.__init__(self, fileName)
1688 def open(self, startFrame=1, endFrame=1):
1689 """Do some initialization operations.
1691 VectorWriter.open(self, startFrame, endFrame)
1692 size = (self.canvasSize[0], self.canvasSize[1])
1693 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1696 """Do some finalization operation.
1700 # remember to call the close method of the parent
1701 VectorWriter.close(self)
1703 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1704 showHiddenEdges=False):
1705 """Convert the scene representation to SVG.
1707 context = scene.getRenderingContext()
1708 framenumber = context.currentFrame()
1710 Objects = scene.getChildren()
1714 if(obj.getType() != 'Mesh'):
1717 mesh = obj.getData(mesh=1)
1720 self._printPolygons(mesh)
1723 self._printEdges(mesh, showHiddenEdges)
1725 self.canvas.showPage()
1731 def _calcCanvasCoord(self, v):
1732 """Convert vertex in scene coordinates to canvas coordinates.
1735 pt = Vector([0, 0, 0])
1737 mW = float(self.canvasSize[0])/2.0
1738 mH = float(self.canvasSize[1])/2.0
1740 # rescale to canvas size
1741 pt[0] = v.co[0]*mW + mW
1742 pt[1] = v.co[1]*mH + mH
1745 # For now we want (0,0) in the top-left corner of the canvas.
1746 # Mirror and translate along y
1748 pt[1] += self.canvasSize[1]
1752 def _printPolygons(self, mesh):
1753 """Print the selected (visible) polygons.
1756 if len(mesh.faces) == 0:
1759 for face in mesh.faces:
1765 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1768 color = [1, 1, 1, 1]
1770 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1772 self.canvas.setStrokeColorRGB(0, 0, 0)
1774 path = self.canvas.beginPath()
1776 # The starting point of the path
1777 p0 = self._calcCanvasCoord(face.verts[0])
1778 path.moveTo(p0[0], p0[1])
1780 for v in face.verts[1:]:
1781 p = self._calcCanvasCoord(v)
1782 path.lineTo(p[0], p[1])
1787 self.canvas.drawPath(path, stroke=0, fill=1)
1789 def _printEdges(self, mesh, showHiddenEdges=False):
1790 """Print the wireframe using mesh edges.
1793 stroke_width = config.edges['WIDTH']
1794 stroke_col = config.edges['COLOR']
1796 self.canvas.setLineCap(1)
1797 self.canvas.setLineJoin(1)
1798 self.canvas.setLineWidth(stroke_width)
1799 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1802 for e in mesh.edges:
1804 self.canvas.setLineWidth(stroke_width)
1807 if showHiddenEdges == False:
1810 # PDF does not support dashed lines natively, so -for now-
1811 # draw hidden lines thinner
1812 self.canvas.setLineWidth(stroke_width/2.0)
1814 p1 = self._calcCanvasCoord(e.v1)
1815 p2 = self._calcCanvasCoord(e.v2)
1817 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1821 # ---------------------------------------------------------------------
1823 ## Rendering Classes
1825 # ---------------------------------------------------------------------
1827 # A dictionary to collect different shading style methods
1828 shadingStyles = dict()
1829 shadingStyles['FLAT'] = None
1830 shadingStyles['TOON'] = None
1832 # A dictionary to collect different edge style methods
1834 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1835 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1837 # A dictionary to collect the supported output formats
1838 outputWriters = dict()
1839 outputWriters['SVG'] = SVGVectorWriter
1841 outputWriters['SWF'] = SWFVectorWriter
1843 outputWriters['PDF'] = PDFVectorWriter
1847 """Render a scene viewed from the active camera.
1849 This class is responsible of the rendering process, transformation and
1850 projection of the objects in the scene are invoked by the renderer.
1852 The rendering is done using the active camera for the current scene.
1856 """Make the rendering process only for the current scene by default.
1858 We will work on a copy of the scene, to be sure that the current scene do
1859 not get modified in any way.
1862 # Render the current Scene, this should be a READ-ONLY property
1863 self._SCENE = Scene.GetCurrent()
1865 # Use the aspect ratio of the scene rendering context
1866 context = self._SCENE.getRenderingContext()
1868 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1869 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1870 float(context.aspectRatioY())
1873 # Render from the currently active camera
1874 #self.cameraObj = self._SCENE.getCurrentCamera()
1876 # Get the list of lighting sources
1877 obj_lst = self._SCENE.getChildren()
1878 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
1880 # When there are no lights we use a default lighting source
1881 # that have the same position of the camera
1882 if len(self.lights) == 0:
1883 l = Lamp.New('Lamp')
1884 lobj = Object.New('Lamp')
1885 lobj.loc = self.cameraObj.loc
1887 self.lights.append(lobj)
1894 def doRendering(self, outputWriter, animation=False):
1895 """Render picture or animation and write it out.
1898 - a Vector writer object that will be used to output the result.
1899 - a flag to tell if we want to render an animation or only the
1903 context = self._SCENE.getRenderingContext()
1904 origCurrentFrame = context.currentFrame()
1906 # Handle the animation case
1908 startFrame = origCurrentFrame
1909 endFrame = startFrame
1912 startFrame = context.startFrame()
1913 endFrame = context.endFrame()
1914 outputWriter.open(startFrame, endFrame)
1916 # Do the rendering process frame by frame
1917 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1918 for f in xrange(startFrame, endFrame+1):
1919 print "\n\nFrame: %d" % f
1921 # FIXME To get the correct camera position we have to use +1 here.
1922 # Is there a bug somewhere in the Scene module?
1923 context.currentFrame(f+1)
1924 self.cameraObj = self._SCENE.getCurrentCamera()
1926 # Use some temporary workspace, a full copy of the scene
1927 inputScene = self._SCENE.copy(2)
1929 # To get the objects at this frame remove the +1 ...
1930 ctx = inputScene.getRenderingContext()
1934 # Get a projector for this camera.
1935 # NOTE: the projector wants object in world coordinates,
1936 # so we should remember to apply modelview transformations
1937 # _before_ we do projection transformations.
1938 self.proj = Projector(self.cameraObj, self.canvasRatio)
1941 renderedScene = self.doRenderScene(inputScene)
1943 print "There was an error! Aborting."
1945 print traceback.print_exc()
1947 self._SCENE.makeCurrent()
1948 Scene.unlink(inputScene)
1952 outputWriter.printCanvas(renderedScene,
1953 doPrintPolygons = config.polygons['SHOW'],
1954 doPrintEdges = config.edges['SHOW'],
1955 showHiddenEdges = config.edges['SHOW_HIDDEN'])
1957 # delete the rendered scene
1958 self._SCENE.makeCurrent()
1959 Scene.unlink(renderedScene)
1962 outputWriter.close()
1964 context.currentFrame(origCurrentFrame)
1967 def doRenderScene(self, workScene):
1968 """Control the rendering process.
1970 Here we control the entire rendering process invoking the operation
1971 needed to transform and project the 3D scene in two dimensions.
1974 # global processing of the scene
1976 self._doSceneClipping(workScene)
1978 self._doConvertGeometricObjsToMesh(workScene)
1980 if config.output['JOIN_OBJECTS']:
1981 self._joinMeshObjectsInScene(workScene)
1983 self._doSceneDepthSorting(workScene)
1985 # Per object activities
1987 Objects = workScene.getChildren()
1988 print "Total Objects: %d" % len(Objects)
1989 for i,obj in enumerate(Objects):
1991 print "Rendering Object: %d" % i
1993 if obj.getType() != 'Mesh':
1994 print "Only Mesh supported! - Skipping type:", obj.getType()
1997 print "Rendering: ", obj.getName()
1999 mesh = obj.getData(mesh=1)
2001 self._doModelingTransformation(mesh, obj.matrix)
2003 self._doBackFaceCulling(mesh)
2006 # When doing HSR with NEWELL we may want to flip all normals
2008 if config.polygons['HSR'] == "NEWELL":
2009 for f in mesh.faces:
2012 for f in mesh.faces:
2015 self._doLighting(mesh)
2017 # Do "projection" now so we perform further processing
2018 # in Normalized View Coordinates
2019 self._doProjection(mesh, self.proj)
2021 self._doViewFrustumClipping(mesh)
2023 self._doHiddenSurfaceRemoval(mesh)
2025 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2027 # Update the object data, important! :)
2039 def _getObjPosition(self, obj):
2040 """Return the obj position in World coordinates.
2042 return obj.matrix.translationPart()
2044 def _cameraViewVector(self):
2045 """Get the View Direction form the camera matrix.
2047 return Vector(self.cameraObj.matrix[2]).resize3D()
2052 def _isFaceVisible(self, face):
2053 """Determine if a face of an object is visible from the current camera.
2055 The view vector is calculated from the camera location and one of the
2056 vertices of the face (expressed in World coordinates, after applying
2057 modelview transformations).
2059 After those transformations we determine if a face is visible by
2060 computing the angle between the face normal and the view vector, this
2061 angle has to be between -90 and 90 degrees for the face to be visible.
2062 This corresponds somehow to the dot product between the two, if it
2063 results > 0 then the face is visible.
2065 There is no need to normalize those vectors since we are only interested in
2066 the sign of the cross product and not in the product value.
2068 NOTE: here we assume the face vertices are in WorldCoordinates, so
2069 please transform the object _before_ doing the test.
2072 normal = Vector(face.no)
2073 camPos = self._getObjPosition(self.cameraObj)
2076 # View Vector in orthographics projections is the view Direction of
2078 if self.cameraObj.data.getType() == 1:
2079 view_vect = self._cameraViewVector()
2081 # View vector in perspective projections can be considered as
2082 # the difference between the camera position and one point of
2083 # the face, we choose the farthest point from the camera.
2084 if self.cameraObj.data.getType() == 0:
2085 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2089 # if d > 0 the face is visible from the camera
2090 d = view_vect * normal
2100 def _doSceneClipping(self, scene):
2101 """Clip whole objects against the View Frustum.
2103 For now clip away only objects according to their center position.
2106 cam_pos = self._getObjPosition(self.cameraObj)
2107 view_vect = self._cameraViewVector()
2109 near = self.cameraObj.data.clipStart
2110 far = self.cameraObj.data.clipEnd
2112 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2113 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2114 fovy = fovy * 360.0/pi
2116 Objects = scene.getChildren()
2118 if o.getType() != 'Mesh': continue;
2121 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2123 d = obj_vect*view_vect
2124 theta = AngleBetweenVecs(obj_vect, view_vect)
2126 # if the object is outside the view frustum, clip it away
2127 if (d < near) or (d > far) or (theta > fovy):
2131 # Use the object bounding box
2132 # (whose points are already in WorldSpace Coordinate)
2134 bb = o.getBoundBox()
2138 p_vect = Vector(cam_pos) - Vector(p)
2140 d = p_vect * view_vect
2141 theta = AngleBetweenVecs(p_vect, view_vect)
2143 # Is this point outside the view frustum?
2144 if (d < near) or (d > far) or (theta > fovy):
2147 # If the bb is all outside the view frustum we clip the whole
2149 if points_outside == len(bb):
2154 def _doConvertGeometricObjsToMesh(self, scene):
2155 """Convert all "geometric" objects to mesh ones.
2157 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2158 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2160 Objects = scene.getChildren()
2161 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2164 obj = self._convertToRawMeshObj(obj)
2166 scene.unlink(old_obj)
2169 # XXX Workaround for Text and Curve which have some normals
2170 # inverted when they are converted to Mesh, REMOVE that when
2171 # blender will fix that!!
2172 if old_obj.getType() in ['Curve', 'Text']:
2173 me = obj.getData(mesh=1)
2174 for f in me.faces: f.sel = 1;
2175 for v in me.verts: v.sel = 1;
2182 def _doSceneDepthSorting(self, scene):
2183 """Sort objects in the scene.
2185 The object sorting is done accordingly to the object centers.
2188 c = self._getObjPosition(self.cameraObj)
2190 by_obj_center_pos = (lambda o1, o2:
2191 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2192 cmp((self._getObjPosition(o1) - Vector(c)).length,
2193 (self._getObjPosition(o2) - Vector(c)).length)
2196 # Implement sorting by bounding box, the object with the bb
2197 # nearest to the camera should be drawn as last.
2198 by_nearest_bbox_point = (lambda o1, o2:
2199 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2200 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2201 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2206 Objects = scene.getChildren()
2207 #Objects.sort(by_obj_center_pos)
2208 Objects.sort(by_nearest_bbox_point)
2215 def _joinMeshObjectsInScene(self, scene):
2216 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2219 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
2221 # FIXME: Object.join() do not work if the list contains 1 object
2225 mesh = Mesh.New('BigOne')
2226 bigObj = Object.New('Mesh', 'BigOne')
2233 except RuntimeError:
2234 print "\nWarning! - Can't Join Objects\n"
2235 scene.unlink(bigObj)
2238 print "Objects Type error?"
2246 # Per object/mesh methods
2248 def _convertToRawMeshObj(self, object):
2249 """Convert geometry based object to a mesh object.
2251 me = Mesh.New('RawMesh_'+object.name)
2252 me.getFromObject(object.name)
2254 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2257 # If the object has no materials set a default material
2258 if not me.materials:
2259 me.materials = [Material.New()]
2260 #for f in me.faces: f.mat = 0
2262 newObject.setMatrix(object.getMatrix())
2266 def _doModelingTransformation(self, mesh, matrix):
2267 """Transform object coordinates to world coordinates.
2269 This step is done simply applying to the object its tranformation
2270 matrix and recalculating its normals.
2272 # XXX FIXME: blender do not transform normals in the right way when
2273 # there are negative scale values
2274 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2275 print "WARNING: Negative scales, expect incorrect results!"
2277 mesh.transform(matrix, True)
2279 def _doBackFaceCulling(self, mesh):
2280 """Simple Backface Culling routine.
2282 At this level we simply do a visibility test face by face and then
2283 select the vertices belonging to visible faces.
2286 # Select all vertices, so edges can be displayed even if there are no
2288 for v in mesh.verts:
2291 Mesh.Mode(Mesh.SelectModes['FACE'])
2293 for f in mesh.faces:
2295 if self._isFaceVisible(f):
2298 def _doLighting(self, mesh):
2299 """Apply an Illumination and shading model to the object.
2301 The model used is the Phong one, it may be inefficient,
2302 but I'm just learning about rendering and starting from Phong seemed
2303 the most natural way.
2306 # If the mesh has vertex colors already, use them,
2307 # otherwise turn them on and do some calculations
2308 if mesh.vertexColors:
2310 mesh.vertexColors = 1
2312 materials = mesh.materials
2314 camPos = self._getObjPosition(self.cameraObj)
2316 # We do per-face color calculation (FLAT Shading), we can easily turn
2317 # to a per-vertex calculation if we want to implement some shading
2318 # technique. For an example see:
2319 # http://www.miralab.unige.ch/papers/368.pdf
2320 for f in mesh.faces:
2326 mat = materials[f.mat]
2328 # A new default material
2330 mat = Material.New('defMat')
2332 # Check if it is a shadeless material
2333 elif mat.getMode() & Material.Modes['SHADELESS']:
2335 # Convert to a value between 0 and 255
2336 tmp_col = [ int(c * 255.0) for c in I]
2347 # do vertex color calculation
2349 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2351 for l in self.lights:
2353 light_pos = self._getObjPosition(l)
2354 light = light_obj.getData()
2356 L = Vector(light_pos).normalize()
2358 V = (Vector(camPos) - Vector(f.cent)).normalize()
2360 N = Vector(f.no).normalize()
2362 if config.polygons['SHADING'] == 'TOON':
2363 NL = ShadingUtils.toonShading(N*L)
2367 # Should we use NL instead of (N*L) here?
2368 R = 2 * (N*L) * N - L
2370 Ip = light.getEnergy()
2372 # Diffuse co-efficient
2373 kd = mat.getRef() * Vector(mat.getRGBCol())
2375 kd[i] *= light.col[i]
2377 Idiff = Ip * kd * max(0, NL)
2380 # Specular component
2381 ks = mat.getSpec() * Vector(mat.getSpecCol())
2382 ns = mat.getHardness()
2383 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2385 TotDiffSpec += (Idiff+Ispec)
2389 Iamb = Vector(Blender.World.Get()[0].getAmb())
2392 # Emissive component (convert to a triplet)
2393 ki = Vector([mat.getEmit()]*3)
2395 #I = ki + Iamb + (Idiff + Ispec)
2396 I = ki + (ka * Iamb) + TotDiffSpec
2399 # Set Alpha component
2401 I.append(mat.getAlpha())
2403 # Clamp I values between 0 and 1
2404 I = [ min(c, 1) for c in I]
2405 I = [ max(0, c) for c in I]
2407 # Convert to a value between 0 and 255
2408 tmp_col = [ int(c * 255.0) for c in I]
2416 def _doProjection(self, mesh, projector):
2417 """Apply Viewing and Projection tranformations.
2420 for v in mesh.verts:
2421 p = projector.doProjection(v.co[:])
2426 #mesh.recalcNormals()
2429 # We could reeset Camera matrix, since now
2430 # we are in Normalized Viewing Coordinates,
2431 # but doung that would affect World Coordinate
2432 # processing for other objects
2434 #self.cameraObj.data.type = 1
2435 #self.cameraObj.data.scale = 2.0
2436 #m = Matrix().identity()
2437 #self.cameraObj.setMatrix(m)
2439 def _doViewFrustumClipping(self, mesh):
2440 """Clip faces against the View Frustum.
2443 # The Canonical View Volume, 8 vertices, and 6 faces,
2444 # We consider its face normals pointing outside
2446 v1 = NMesh.Vert(1, 1, -1)
2447 v2 = NMesh.Vert(1, -1, -1)
2448 v3 = NMesh.Vert(-1, -1, -1)
2449 v4 = NMesh.Vert(-1, 1, -1)
2450 v5 = NMesh.Vert(1, 1, 1)
2451 v6 = NMesh.Vert(1, -1, 1)
2452 v7 = NMesh.Vert(-1, -1, 1)
2453 v8 = NMesh.Vert(-1, 1, 1)
2456 f1 = NMesh.Face([v1, v4, v3, v2])
2458 f2 = NMesh.Face([v5, v6, v7, v8])
2460 f3 = NMesh.Face([v1, v2, v6, v5])
2462 f4 = NMesh.Face([v2, v3, v7, v6])
2464 f5 = NMesh.Face([v3, v4, v8, v7])
2466 f6 = NMesh.Face([v4, v1, v5, v8])
2469 nmesh = NMesh.GetRaw(mesh.name)
2470 clippedfaces = nmesh.faces[:]
2471 facelist = clippedfaces[:]
2473 for clipface in cvv:
2479 newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2482 # Check if the face is all outside the view frustum
2483 # TODO: Do this test before, it is more efficient
2486 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2489 if points_outside != len(f):
2490 clippedfaces.append(f)
2494 nmesh.verts.append(v)
2498 nf.col = [f.col[0]] * len(nf.v)
2500 clippedfaces.append(nf)
2501 facelist = clippedfaces[:]
2504 nmesh.faces = facelist
2509 def __simpleDepthSort(self, mesh):
2510 """Sort faces by the furthest vertex.
2512 This simple mesthod is known also as the painter algorithm, and it
2513 solves HSR correctly only for convex meshes.
2518 # The sorting requires circa n*log(n) steps
2520 progress.setActivity("HSR: Painter", n*log(n))
2522 by_furthest_z = (lambda f1, f2: progress.update() and
2523 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2526 # FIXME: using NMesh to sort faces. We should avoid that!
2527 nmesh = NMesh.GetRaw(mesh.name)
2529 # remember that _higher_ z values mean further points
2530 nmesh.faces.sort(by_furthest_z)
2531 nmesh.faces.reverse()
2536 def __newellDepthSort(self, mesh):
2537 """Newell's depth sorting.
2543 # Find non planar quads and convert them to triangle
2544 #for f in mesh.faces:
2546 # if is_nonplanar_quad(f.v):
2547 # print "NON QUAD??"
2551 # Now reselect all faces
2552 for f in mesh.faces:
2554 mesh.quadToTriangle()
2556 # FIXME: using NMesh to sort faces. We should avoid that!
2557 nmesh = NMesh.GetRaw(mesh.name)
2559 # remember that _higher_ z values mean further points
2560 nmesh.faces.sort(by_furthest_z)
2561 nmesh.faces.reverse()
2563 # Begin depth sort tests
2565 # use the smooth flag to set marked faces
2566 for f in nmesh.faces:
2569 facelist = nmesh.faces[:]
2573 # The steps are _at_least_ equal to len(facelist), we do not count the
2574 # feces coming out from splitting!!
2575 progress.setActivity("HSR: Newell", len(facelist))
2576 #progress.setQuiet(True)
2579 while len(facelist):
2580 debug("\n----------------------\n")
2581 debug("len(facelits): %d\n" % len(facelist))
2584 pSign = sign(P.normal[2])
2586 # We can discard faces parallel to the view vector
2587 #if P.normal[2] == 0:
2588 # facelist.remove(P)
2594 for Q in facelist[1:]:
2596 debug("P.smooth: " + str(P.smooth) + "\n")
2597 debug("Q.smooth: " + str(Q.smooth) + "\n")
2600 qSign = sign(Q.normal[2])
2601 # TODO: check also if Q is parallel??
2603 # Test 0: We need to test only those Qs whose furthest vertex
2604 # is closer to the observer than the closest vertex of P.
2606 zP = [v.co[2] for v in P.v]
2607 zQ = [v.co[2] for v in Q.v]
2608 notZOverlap = min(zP) > max(zQ) + EPS
2612 debug("NOT Z OVERLAP!\n")
2614 # If Q is not marked then we can safely print P
2617 debug("met a marked face\n")
2621 # Test 1: X extent overlapping
2622 xP = [v.co[0] for v in P.v]
2623 xQ = [v.co[0] for v in Q.v]
2624 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2625 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2629 debug("NOT X OVERLAP!\n")
2633 # Test 2: Y extent Overlapping
2634 yP = [v.co[1] for v in P.v]
2635 yQ = [v.co[1] for v in Q.v]
2636 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2637 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2641 debug("NOT Y OVERLAP!\n")
2645 # Test 3: P vertices are all behind the plane of Q
2648 d = qSign * HSR.Distance(Vector(Pi), Q)
2651 pVerticesBehindPlaneQ = (n == len(P))
2653 if pVerticesBehindPlaneQ:
2655 debug("P BEHIND Q!\n")
2659 # Test 4: Q vertices in front of the plane of P
2662 d = pSign * HSR.Distance(Vector(Qi), P)
2665 qVerticesInFrontPlaneP = (n == len(Q))
2667 if qVerticesInFrontPlaneP:
2669 debug("Q IN FRONT OF P!\n")
2673 # Test 5: Check if projections of polygons effectively overlap,
2674 # in previous tests we checked only bounding boxes.
2676 #if not projectionsOverlap(P, Q):
2677 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2679 debug("Projections do not overlap!\n")
2682 # We still can't say if P obscures Q.
2684 # But if Q is marked we do a face-split trying to resolve a
2685 # difficulty (maybe a visibility cycle).
2688 debug("Possibly a cycle detected!\n")
2689 debug("Split here!!\n")
2691 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2695 # The question now is: Does Q obscure P?
2698 # Test 3bis: Q vertices are all behind the plane of P
2701 d = pSign * HSR.Distance(Vector(Qi), P)
2704 qVerticesBehindPlaneP = (n == len(Q))
2706 if qVerticesBehindPlaneP:
2707 debug("\nTest 3bis\n")
2708 debug("Q BEHIND P!\n")
2711 # Test 4bis: P vertices in front of the plane of Q
2714 d = qSign * HSR.Distance(Vector(Pi), Q)
2717 pVerticesInFrontPlaneQ = (n == len(P))
2719 if pVerticesInFrontPlaneQ:
2720 debug("\nTest 4bis\n")
2721 debug("P IN FRONT OF Q!\n")
2724 # We don't even know if Q does obscure P, so they should
2725 # intersect each other, split one of them in two parts.
2726 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2727 debug("\nSimple Intersection?\n")
2728 debug("Test 3bis or 4bis failed\n")
2729 debug("Split here!!2\n")
2731 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2736 facelist.insert(0, Q)
2739 debug("Q marked!\n")
2743 if split_done == 0 and face_marked == 0:
2746 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2750 if len(facelist) == 870:
2751 dumpfaces([P, Q], "loopdebug.svg")
2754 #if facelist == None:
2756 # print [v.co for v in P]
2757 # print [v.co for v in Q]
2760 # end of while len(facelist)
2763 nmesh.faces = maplist
2764 #for f in nmesh.faces:
2770 def _doHiddenSurfaceRemoval(self, mesh):
2771 """Do HSR for the given mesh.
2773 if len(mesh.faces) == 0:
2776 if config.polygons['HSR'] == 'PAINTER':
2777 print "\nUsing the Painter algorithm for HSR."
2778 self.__simpleDepthSort(mesh)
2780 elif config.polygons['HSR'] == 'NEWELL':
2781 print "\nUsing the Newell's algorithm for HSR."
2782 self.__newellDepthSort(mesh)
2785 def _doEdgesStyle(self, mesh, edgestyleSelect):
2786 """Process Mesh Edges accroding to a given selection style.
2788 Examples of algorithms:
2791 given an edge if its adjacent faces have the same normal (that is
2792 they are complanar), than deselect it.
2795 given an edge if one its adjacent faces is frontfacing and the
2796 other is backfacing, than select it, else deselect.
2799 Mesh.Mode(Mesh.SelectModes['EDGE'])
2801 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2803 for i,edge_faces in enumerate(edge_cache):
2804 mesh.edges[i].sel = 0
2805 if edgestyleSelect(edge_faces):
2806 mesh.edges[i].sel = 1
2809 for e in mesh.edges:
2812 if edgestyleSelect(e, mesh):
2818 # ---------------------------------------------------------------------
2820 ## GUI Class and Main Program
2822 # ---------------------------------------------------------------------
2825 from Blender import BGL, Draw
2826 from Blender.BGL import *
2832 # Output Format menu
2833 output_format = config.output['FORMAT']
2834 default_value = outputWriters.keys().index(output_format)+1
2835 GUI.outFormatMenu = Draw.Create(default_value)
2836 GUI.evtOutFormatMenu = 0
2838 # Animation toggle button
2839 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2840 GUI.evtAnimToggle = 1
2842 # Join Objects toggle button
2843 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2844 GUI.evtJoinObjsToggle = 2
2846 # Render filled polygons
2847 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2849 # Shading Style menu
2850 shading_style = config.polygons['SHADING']
2851 default_value = shadingStyles.keys().index(shading_style)+1
2852 GUI.shadingStyleMenu = Draw.Create(default_value)
2853 GUI.evtShadingStyleMenu = 21
2855 GUI.evtPolygonsToggle = 3
2856 # We hide the config.polygons['EXPANSION_TRICK'], for now
2858 # Render polygon edges
2859 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2860 GUI.evtShowEdgesToggle = 4
2862 # Render hidden edges
2863 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2864 GUI.evtShowHiddenEdgesToggle = 5
2867 edge_style = config.edges['STYLE']
2868 default_value = edgeStyles.keys().index(edge_style)+1
2869 GUI.edgeStyleMenu = Draw.Create(default_value)
2870 GUI.evtEdgeStyleMenu = 6
2873 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2874 GUI.evtEdgeWidthSlider = 7
2877 c = config.edges['COLOR']
2878 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2879 GUI.evtEdgeColorPicker = 71
2882 GUI.evtRenderButton = 8
2885 GUI.evtExitButton = 9
2889 # initialize static members
2892 glClear(GL_COLOR_BUFFER_BIT)
2893 glColor3f(0.0, 0.0, 0.0)
2894 glRasterPos2i(10, 350)
2895 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2897 glRasterPos2i(10, 335)
2898 Draw.Text("Press Q or ESC to quit.")
2900 # Build the output format menu
2901 glRasterPos2i(10, 310)
2902 Draw.Text("Select the output Format:")
2903 outMenuStruct = "Output Format %t"
2904 for t in outputWriters.keys():
2905 outMenuStruct = outMenuStruct + "|%s" % t
2906 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2907 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2910 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2911 10, 260, 160, 18, GUI.animToggle.val,
2912 "Toggle rendering of animations")
2914 # Join Objects toggle
2915 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2916 10, 235, 160, 18, GUI.joinObjsToggle.val,
2917 "Join objects in the rendered file")
2920 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2922 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2925 glRasterPos2i(200, 310)
2926 Draw.Text("Rendering Style:")
2929 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
2930 200, 285, 160, 18, GUI.polygonsToggle.val,
2931 "Render filled polygons")
2933 if GUI.polygonsToggle.val == 1:
2935 # Polygon Shading Style
2936 shadingStyleMenuStruct = "Shading Style %t"
2937 for t in shadingStyles.keys():
2938 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
2939 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
2940 200, 260, 160, 18, GUI.shadingStyleMenu.val,
2941 "Choose the shading style")
2945 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
2946 200, 235, 160, 18, GUI.showEdgesToggle.val,
2947 "Render polygon edges")
2949 if GUI.showEdgesToggle.val == 1:
2952 edgeStyleMenuStruct = "Edge Style %t"
2953 for t in edgeStyles.keys():
2954 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
2955 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
2956 200, 210, 160, 18, GUI.edgeStyleMenu.val,
2957 "Choose the edge style")
2960 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
2961 200, 185, 140, 18, GUI.edgeWidthSlider.val,
2962 0.0, 10.0, 0, "Change Edge Width")
2965 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
2966 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
2969 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
2970 GUI.evtShowHiddenEdgesToggle,
2971 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
2972 "Render hidden edges as dashed lines")
2974 glRasterPos2i(10, 160)
2975 Draw.Text("%s (c) 2006" % __author__)
2977 def event(evt, val):
2979 if evt == Draw.ESCKEY or evt == Draw.QKEY:
2986 def button_event(evt):
2988 if evt == GUI.evtExitButton:
2991 elif evt == GUI.evtOutFormatMenu:
2992 i = GUI.outFormatMenu.val - 1
2993 config.output['FORMAT']= outputWriters.keys()[i]
2994 # Set the new output file
2996 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
2998 elif evt == GUI.evtAnimToggle:
2999 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3001 elif evt == GUI.evtJoinObjsToggle:
3002 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3004 elif evt == GUI.evtPolygonsToggle:
3005 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3007 elif evt == GUI.evtShadingStyleMenu:
3008 i = GUI.shadingStyleMenu.val - 1
3009 config.polygons['SHADING'] = shadingStyles.keys()[i]
3011 elif evt == GUI.evtShowEdgesToggle:
3012 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3014 elif evt == GUI.evtShowHiddenEdgesToggle:
3015 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3017 elif evt == GUI.evtEdgeStyleMenu:
3018 i = GUI.edgeStyleMenu.val - 1
3019 config.edges['STYLE'] = edgeStyles.keys()[i]
3021 elif evt == GUI.evtEdgeWidthSlider:
3022 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3024 elif evt == GUI.evtEdgeColorPicker:
3025 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3027 elif evt == GUI.evtRenderButton:
3028 label = "Save %s" % config.output['FORMAT']
3029 # Show the File Selector
3031 Blender.Window.FileSelector(vectorize, label, outputfile)
3034 print "Event: %d not handled!" % evt
3041 from pprint import pprint
3043 pprint(config.output)
3044 pprint(config.polygons)
3045 pprint(config.edges)
3047 _init = staticmethod(_init)
3048 draw = staticmethod(draw)
3049 event = staticmethod(event)
3050 button_event = staticmethod(button_event)
3051 conf_debug = staticmethod(conf_debug)
3053 # A wrapper function for the vectorizing process
3054 def vectorize(filename):
3055 """The vectorizing process is as follows:
3057 - Instanciate the writer and the renderer
3062 print "\nERROR: invalid file name!"
3065 from Blender import Window
3066 editmode = Window.EditMode()
3067 if editmode: Window.EditMode(0)
3069 actualWriter = outputWriters[config.output['FORMAT']]
3070 writer = actualWriter(filename)
3072 renderer = Renderer()
3073 renderer.doRendering(writer, config.output['ANIMATION'])
3075 if editmode: Window.EditMode(1)
3080 if __name__ == "__main__":
3085 basename = Blender.sys.basename(Blender.Get('filename'))
3087 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3089 if Blender.mode == 'background':
3090 progress = ConsoleProgressIndicator()
3091 vectorize(outputfile)
3093 progress = GraphicalProgressIndicator()
3094 Draw.Register(GUI.draw, GUI.event, GUI.button_event)