6 Tooltip: 'Vector Rendering Method script'
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://vrm.ao2.it"]
11 __version__ = "0.3.beta"
14 Render the scene and save the result in vector format.
17 # ---------------------------------------------------------------------
18 # Copyright (c) 2006, 2007, 2008, 2009 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:
46 # - FIX the issue with negative scales in object tranformations!
47 # - Use a better depth sorting algorithm
48 # - Review how selections are made (this script uses selection states of
49 # primitives to represent visibility infos)
50 # - Use a data structure other than Mesh to represent the 2D image?
51 # Think to a way to merge (adjacent) polygons that have the same color.
52 # Or a way to use paths for silhouettes and contours.
53 # - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
54 # not support SMIL for animations)
55 # - Switch to the Mesh structure, should be considerably faster
56 # (partially done, but with Mesh we cannot sort faces, yet)
57 # - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
58 # - Implement Shading Styles? (partially done, to make more flexible).
59 # - Add Vector Writers other than SVG.
60 # - set the background color!
61 # - Check memory use!!
63 # ---------------------------------------------------------------------
66 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
67 from Blender.Mathutils import *
74 from sets import Set as set
79 return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
80 # in python > 2.4 we ca use the following
81 #return [ u for u in alist if u not in locals()['_[1]'] ]
87 # We use a global progress Indicator Object
91 # Config class for global settings
95 polygons['SHOW'] = True
96 polygons['SHADING'] = 'FLAT' # FLAT or TOON
97 polygons['HSR'] = 'PAINTER' # PAINTER or NEWELL
98 # Hidden to the user for now
99 polygons['EXPANSION_TRICK'] = True
101 polygons['TOON_LEVELS'] = 2
104 edges['SHOW'] = False
105 edges['SHOW_HIDDEN'] = False
106 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
108 edges['COLOR'] = [0, 0, 0]
111 output['FORMAT'] = 'SVG'
112 output['ANIMATION'] = False
113 output['JOIN_OBJECTS'] = True
115 def saveToRegistry():
118 for k,v in config.__dict__.iteritems():
120 # config class store settings in dictionaries
121 if v.__class__ == dict().__class__:
123 regkey_prefix = k.upper()+"_"
125 for opt_k,opt_v in v.iteritems():
126 regkey = regkey_prefix + opt_k
128 registry[regkey] = opt_v
130 Blender.Registry.SetKey('VRM', registry, True)
132 saveToRegistry = staticmethod(saveToRegistry)
134 def loadFromRegistry():
135 registry = Blender.Registry.GetKey('VRM', True)
139 for k,v in registry.iteritems():
141 conf_attr = k_tmp[0].lower()
142 conf_key = str.join("_",k_tmp[1:])
145 if config.__dict__.has_key(conf_attr):
146 config.__dict__[conf_attr][conf_key] = conf_val
148 loadFromRegistry = staticmethod(loadFromRegistry)
154 def dumpfaces(flist, filename):
155 """Dump a single face to a file.
166 writerobj = SVGVectorWriter(filename)
169 writerobj._printPolygons(m)
175 sys.stderr.write(msg)
178 return (abs(v1[0]-v2[0]) < EPS and
179 abs(v1[1]-v2[1]) < EPS )
180 by_furthest_z = (lambda f1, f2:
181 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
196 # ---------------------------------------------------------------------
200 # ---------------------------------------------------------------------
206 """A utility class for HSR processing.
209 def is_nonplanar_quad(face):
210 """Determine if a quad is non-planar.
212 From: http://mathworld.wolfram.com/Coplanar.html
214 Geometric objects lying in a common plane are said to be coplanar.
215 Three noncollinear points determine a plane and so are trivially coplanar.
216 Four points are coplanar iff the volume of the tetrahedron defined by them is
222 | x_4 y_4 z_4 1 | == 0
224 Coplanarity is equivalent to the statement that the pair of lines
225 determined by the four points are not skew, and can be equivalently stated
226 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
228 An arbitrary number of n points x_1, ..., x_n can be tested for
229 coplanarity by finding the point-plane distances of the points
230 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
231 and checking if they are all zero.
232 If so, the points are all coplanar.
234 We here check only for 4-point complanarity.
240 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
244 # three points must be complanar
247 x1 = Vector(face[0].co)
248 x2 = Vector(face[1].co)
249 x3 = Vector(face[2].co)
250 x4 = Vector(face[3].co)
252 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
258 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
260 def pointInPolygon(poly, v):
263 pointInPolygon = staticmethod(pointInPolygon)
265 def edgeIntersection(s1, s2, do_perturbate=False):
267 (x1, y1) = s1[0].co[0], s1[0].co[1]
268 (x2, y2) = s1[1].co[0], s1[1].co[1]
270 (x3, y3) = s2[0].co[0], s2[0].co[1]
271 (x4, y4) = s2[1].co[0], s2[1].co[1]
279 # calculate delta values (vector components)
288 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
289 if C == 0: #/* parallel */
292 dx3 = x1 - x3 # /* combined origin offset vector */
295 a1 = (dy3 * dx2 - dx3 * dy2) / C;
296 a2 = (dy3 * dx1 - dx3 * dy1) / C;
298 # check for degeneracies
300 #print_debug(str(a1)+"\n")
301 #print_debug(str(a2)+"\n\n")
303 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
304 # Intersection on boundaries, we consider the point external?
307 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
313 return (NMesh.Vert(x, y, z), a1, a2)
316 # lines have intersections but not those segments
319 edgeIntersection = staticmethod(edgeIntersection)
321 def isVertInside(self, v):
325 # Create point at infinity
326 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
328 for i in range(len(self.v)):
329 s1 = (point_at_infinity, v)
330 s2 = (self.v[i-1], self.v[i])
332 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
335 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
339 if winding_number % 2 == 0 :
346 isVertInside = staticmethod(isVertInside)
350 return ((b[0] - a[0]) * (c[1] - a[1]) -
351 (b[1] - a[1]) * (c[0] - a[0]) )
353 det = staticmethod(det)
355 def pointInPolygon(q, P):
358 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
362 for i in range(len(P.v)):
365 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
366 if det(p0.co, p1.co, q.co) == 0 :
369 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
374 pointInPolygon = staticmethod(pointInPolygon)
376 def projectionsOverlap(f1, f2):
377 """ If you have nonconvex, but still simple polygons, an acceptable method
378 is to iterate over all vertices and perform the Point-in-polygon test[1].
379 The advantage of this method is that you can compute the exact
380 intersection point and collision normal that you will need to simulate
381 collision. When you have the point that lies inside the other polygon, you
382 just iterate over all edges of the second polygon again and look for edge
383 intersections. Note that this method detects collsion when it already
384 happens. This algorithm is fast enough to perform it hundreds of times per
387 for i in range(len(f1.v)):
390 # If a point of f1 in inside f2, there is an overlap!
392 #if HSR.isVertInside(f2, v1):
393 if HSR.pointInPolygon(v1, f2):
396 # If not the polygon can be ovelap as well, so we check for
397 # intersection between an edge of f1 and all the edges of f2
401 for j in range(len(f2.v)):
408 intrs = HSR.edgeIntersection(e1, e2)
410 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
411 # str(v2.co) + " " + str(v3.co) )
412 #print_debug("\nIntersection\n")
418 projectionsOverlap = staticmethod(projectionsOverlap)
420 def midpoint(p1, p2):
421 """Return the midpoint of two vertices.
423 m = MidpointVecs(Vector(p1), Vector(p2))
424 mv = NMesh.Vert(m[0], m[1], m[2])
428 midpoint = staticmethod(midpoint)
430 def facesplit(P, Q, facelist, nmesh):
431 """Split P or Q according to the strategy illustrated in the Newell's
435 by_furthest_z = (lambda f1, f2:
436 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
439 # Choose if split P on Q plane or vice-versa
443 d = HSR.Distance(Vector(Pi), Q)
446 pIntersectQ = (n != len(P))
450 d = HSR.Distance(Vector(Qi), P)
453 qIntersectP = (n != len(Q))
457 # 1. If parts of P lie in both half-spaces of Q
458 # then splice P in two with the plane of Q
464 newfaces = HSR.splitOn(plane, f)
466 # 2. Else if parts of Q lie in both half-space of P
467 # then splice Q in two with the plane of P
468 if qIntersectP and newfaces == None:
473 newfaces = HSR.splitOn(plane, f)
476 # 3. Else slice P in half through the mid-point of
477 # the longest pair of opposite sides
480 print "We ignore P..."
487 # v1 = midpoint(f[0], f[1])
488 # v2 = midpoint(f[1], f[2])
490 # v1 = midpoint(f[0], f[1])
491 # v2 = midpoint(f[2], f[3])
492 #vec3 = (Vector(v2)+10*Vector(f.normal))
494 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
496 #plane = NMesh.Face([v1, v2, v3])
498 #newfaces = splitOn(plane, f)
502 print "Big FAT problem, we weren't able to split POLYGONS!"
508 # if v not in plane and v in nmesh.verts:
509 # nmesh.verts.remove(v)
514 nf.col = [f.col[0]] * len(nf.v)
519 nmesh.verts.append(v)
520 # insert pieces in the list
525 # and resort the faces
526 facelist.sort(by_furthest_z)
527 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
530 #print [ f.smooth for f in facelist ]
534 facesplit = staticmethod(facesplit)
536 def isOnSegment(v1, v2, p, extremes_internal=False):
537 """Check if point p is in segment v1v2.
543 # Should we consider extreme points as internal ?
545 # if p == v1 or p == v2:
546 if l1 < EPS or l2 < EPS:
547 return extremes_internal
551 # if the sum of l1 and l2 is circa l, then the point is on segment,
552 if abs(l - (l1+l2)) < EPS:
557 isOnSegment = staticmethod(isOnSegment)
559 def Distance(point, face):
560 """ Calculate the distance between a point and a face.
562 An alternative but more expensive method can be:
564 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
565 Vector(face.no), Vector(point), 0)
567 d = Vector(ip - point).length
569 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
573 plNormal = Vector(face.no)
574 plVert0 = Vector(face.v[0])
576 d = (plVert0 * plNormal) - (p * plNormal)
578 #d = plNormal * (plVert0 - p)
580 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
584 Distance = staticmethod(Distance)
588 # make one or two new faces based on a list of vertex-indices
617 makeFaces = staticmethod(makeFaces)
619 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
620 """Split P using the plane of Q.
621 Logic taken from the knife.py python script
624 # Check if P and Q are parallel
625 u = CrossVecs(Vector(Q.no),Vector(P.no))
631 print "PARALLEL planes!!"
635 # The final aim is to find the intersection line between P
636 # and the plane of Q, and split P along this line
640 # Calculate point-plane Distance between vertices of P and plane Q
642 for i in range(0, nP):
643 d.append(HSR.Distance(P.v[i], Q))
656 #print "d0:", d0, "d1:", d1
658 # if the vertex lies in the cutplane
660 #print "d1 On cutplane"
661 posVertList.append(V1)
662 negVertList.append(V1)
664 # if the previous vertex lies in cutplane
666 #print "d0 on Cutplane"
668 #print "d1 on positive Halfspace"
669 posVertList.append(V1)
671 #print "d1 on negative Halfspace"
672 negVertList.append(V1)
674 # if they are on the same side of the plane
676 #print "On the same half-space"
678 #print "d1 on positive Halfspace"
679 posVertList.append(V1)
681 #print "d1 on negative Halfspace"
682 negVertList.append(V1)
684 # the vertices are not on the same side of the plane, so we have an intersection
686 #print "Intersection"
688 e = Vector(V0), Vector(V1)
689 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
691 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
696 #print "Intersection", inters
698 nv = NMesh.Vert(inters[0], inters[1], inters[2])
699 newVertList.append(nv)
701 posVertList.append(nv)
702 negVertList.append(nv)
705 posVertList.append(V1)
707 negVertList.append(V1)
710 # uniq for python > 2.4
711 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
712 #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
714 # a more portable way
715 posVertList = uniq(posVertList)
716 negVertList = uniq(negVertList)
719 # If vertex are all on the same half-space, return
720 #if len(posVertList) < 3:
721 # print "Problem, we created a face with less that 3 vertices??"
723 #if len(negVertList) < 3:
724 # print "Problem, we created a face with less that 3 vertices??"
727 if len(posVertList) < 3 or len(negVertList) < 3:
728 #print "RETURN NONE, SURE???"
731 if not return_positive_faces:
733 if not return_negative_faces:
736 newfaces = HSR.addNewFaces(posVertList, negVertList)
740 splitOn = staticmethod(splitOn)
742 def addNewFaces(posVertList, negVertList):
743 # Create new faces resulting from the split
745 if len(posVertList) or len(negVertList):
747 #newfaces = [posVertList] + [negVertList]
748 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
749 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
753 outfaces += HSR.makeFaces(nf)
758 addNewFaces = staticmethod(addNewFaces)
761 # ---------------------------------------------------------------------
763 ## Mesh Utility class
765 # ---------------------------------------------------------------------
769 def buildEdgeFaceUsersCache(me):
771 Takes a mesh and returns a list aligned with the meshes edges.
772 Each item is a list of the faces that use the edge
773 would be the equiv for having ed.face_users as a property
775 Taken from .blender/scripts/bpymodules/BPyMesh.py,
776 thanks to ideasman_42.
779 def sorted_edge_indicies(ed):
787 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
789 fvi= [v.index for v in f.v]# face vert idx's
790 for i in xrange(len(f)):
797 face_edges_dict[i1,i2][1].append(f)
799 face_edges= [None] * len(me.edges)
800 for ed_index, ed_faces in face_edges_dict.itervalues():
801 face_edges[ed_index]= ed_faces
805 def isMeshEdge(adjacent_faces):
808 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
809 Note: if the edge has no adjacent faces we want to show it as well,
810 useful for "edge only" portion of objects.
813 if len(adjacent_faces) == 0:
816 selected_faces = [f for f in adjacent_faces if f.sel]
818 if len(selected_faces) != 0:
823 def isSilhouetteEdge(adjacent_faces):
824 """Silhuette selection rule.
826 An edge is a silhuette edge if it is shared by two faces with
827 different selection status or if it is a boundary edge of a selected
831 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
832 (len(adjacent_faces) == 2 and
833 adjacent_faces[0].sel != adjacent_faces[1].sel)
839 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
840 isMeshEdge = staticmethod(isMeshEdge)
841 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
844 # ---------------------------------------------------------------------
846 ## Shading Utility class
848 # ---------------------------------------------------------------------
854 def toonShadingMapSetup():
855 levels = config.polygons['TOON_LEVELS']
857 texels = 2*levels - 1
858 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
864 shademap = ShadingUtils.shademap
867 shademap = ShadingUtils.toonShadingMapSetup()
870 for i in xrange(0, len(shademap)-1):
871 pivot = (shademap[i]+shademap[i+1])/2.0
876 if v < shademap[i+1]:
881 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
882 toonShading = staticmethod(toonShading)
885 # ---------------------------------------------------------------------
887 ## Projections classes
889 # ---------------------------------------------------------------------
892 """Calculate the projection of an object given the camera.
894 A projector is useful to so some per-object transformation to obtain the
895 projection of an object given the camera.
897 The main method is #doProjection# see the method description for the
901 def __init__(self, cameraObj, canvasRatio):
902 """Calculate the projection matrix.
904 The projection matrix depends, in this case, on the camera settings.
905 TAKE CARE: This projector expects vertices in World Coordinates!
908 camera = cameraObj.getData()
910 aspect = float(canvasRatio[0])/float(canvasRatio[1])
911 near = camera.clipStart
914 scale = float(camera.scale)
916 fovy = atan(0.5/aspect/(camera.lens/32))
917 fovy = fovy * 360.0/pi
920 if Blender.Get('version') < 243:
927 # What projection do we want?
928 if camera.type == camPersp:
929 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
930 elif camera.type == camOrtho:
931 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
934 # View transformation
935 cam = Matrix(cameraObj.getInverseMatrix())
940 self.projectionMatrix = mP
946 def doProjection(self, v):
947 """Project the point on the view plane.
949 Given a vertex calculate the projection using the current projection
953 # Note that we have to work on the vertex using homogeneous coordinates
954 # From blender 2.42+ we don't need to resize the vector to be 4d
955 # when applying a 4x4 matrix, but we do that anyway since we need the
956 # 4th coordinate later
957 p = self.projectionMatrix * Vector(v).resize4D()
959 # Perspective division
976 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
977 """Return a perspective projection matrix.
980 top = near * tan(fovy * pi / 360.0)
984 x = (2.0 * near) / (right-left)
985 y = (2.0 * near) / (top-bottom)
986 a = (right+left) / (right-left)
987 b = (top+bottom) / (top - bottom)
988 c = - ((far+near) / (far-near))
989 d = - ((2*far*near)/(far-near))
995 [0.0, 0.0, -1.0, 0.0])
999 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
1000 """Return an orthogonal projection matrix.
1003 # The 11 in the formula was found emiprically
1004 top = near * tan(fovy * pi / 360.0) * (scale * 11)
1006 left = bottom * aspect
1011 tx = -((right+left)/rl)
1012 ty = -((top+bottom)/tb)
1013 tz = ((far+near)/fn)
1016 [2.0/rl, 0.0, 0.0, tx],
1017 [0.0, 2.0/tb, 0.0, ty],
1018 [0.0, 0.0, 2.0/fn, tz],
1019 [0.0, 0.0, 0.0, 1.0])
1024 # ---------------------------------------------------------------------
1026 ## Progress Indicator
1028 # ---------------------------------------------------------------------
1031 """A model for a progress indicator.
1033 Do the progress calculation calculation and
1034 the view independent stuff of a progress indicator.
1036 def __init__(self, steps=0):
1042 def setSteps(self, steps):
1043 """Set the number of steps of the activity wich we want to track.
1050 def setName(self, name):
1051 """Set the name of the activity wich we want to track.
1058 def getProgress(self):
1059 return self.progress
1066 """Update the model, call this method when one step is completed.
1068 if self.progress == 100:
1072 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1073 self.progress = int(self.progress)
1078 class ProgressIndicator:
1079 """An abstraction of a View for the Progress Model
1083 # Use a refresh rate so we do not show the progress at
1084 # every update, but every 'self.refresh_rate' times.
1085 self.refresh_rate = 10
1086 self.shows_counter = 0
1090 self.progressModel = None
1092 def setQuiet(self, value):
1095 def setActivity(self, name, steps):
1096 """Initialize the Model.
1098 In a future version (with subactivities-progress support) this method
1099 could only set the current activity.
1101 self.progressModel = Progress()
1102 self.progressModel.setName(name)
1103 self.progressModel.setSteps(steps)
1105 def getActivity(self):
1106 return self.progressModel
1109 """Update the model and show the actual progress.
1111 assert(self.progressModel)
1113 if self.progressModel.update():
1117 self.show(self.progressModel.getProgress(),
1118 self.progressModel.getName())
1120 # We return always True here so we can call the update() method also
1121 # from lambda funcs (putting the call in logical AND with other ops)
1124 def show(self, progress, name=""):
1125 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1126 if self.shows_counter != 0:
1130 self.shows_counter = -1
1133 class ConsoleProgressIndicator(ProgressIndicator):
1134 """Show a progress bar on stderr, a la wget.
1137 ProgressIndicator.__init__(self)
1139 self.swirl_chars = ["-", "\\", "|", "/"]
1140 self.swirl_count = -1
1142 def show(self, progress, name):
1143 ProgressIndicator.show(self, progress, name)
1146 bar_progress = int( (progress/100.0) * bar_length )
1147 bar = ("=" * bar_progress).ljust(bar_length)
1149 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1150 swirl_char = self.swirl_chars[self.swirl_count]
1152 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1154 sys.stderr.write(progress_bar+"\r")
1156 sys.stderr.write("\n")
1159 class GraphicalProgressIndicator(ProgressIndicator):
1160 """Interface to the Blender.Window.DrawProgressBar() method.
1163 ProgressIndicator.__init__(self)
1165 #self.swirl_chars = ["-", "\\", "|", "/"]
1166 # We have to use letters with the same width, for now!
1167 # Blender progress bar considers the font widths when
1168 # calculating the progress bar width.
1169 self.swirl_chars = ["\\", "/"]
1170 self.swirl_count = -1
1172 def show(self, progress, name):
1173 ProgressIndicator.show(self, progress)
1175 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1176 swirl_char = self.swirl_chars[self.swirl_count]
1178 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1180 # Finally draw the Progress Bar
1181 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1182 Window.DrawProgressBar(progress/100.0, progress_text)
1185 Window.DrawProgressBar(1, progress_text)
1186 Window.WaitCursor(0)
1190 # ---------------------------------------------------------------------
1192 ## 2D Object representation class
1194 # ---------------------------------------------------------------------
1196 # TODO: a class to represent the needed properties of a 2D vector image
1197 # For now just using a [N]Mesh structure.
1200 # ---------------------------------------------------------------------
1202 ## Vector Drawing Classes
1204 # ---------------------------------------------------------------------
1210 A class for printing output in a vectorial format.
1212 Given a 2D representation of the 3D scene the class is responsible to
1213 write it is a vector format.
1215 Every subclasses of VectorWriter must have at last the following public
1219 - printCanvas(self, scene,
1220 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1223 def __init__(self, fileName):
1224 """Set the output file name and other properties"""
1229 config.writer = dict()
1230 config.writer['SETTING'] = True
1232 self.outputFileName = fileName
1234 context = Scene.GetCurrent().getRenderingContext()
1235 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1237 self.fps = context.fps
1241 self.animation = False
1248 def open(self, startFrame=1, endFrame=1):
1249 if startFrame != endFrame:
1250 self.startFrame = startFrame
1251 self.endFrame = endFrame
1252 self.animation = True
1254 print "Outputting to: ", self.outputFileName
1261 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1262 showHiddenEdges=False):
1263 """This is the interface for the needed printing routine.
1270 class SVGVectorWriter(VectorWriter):
1271 """A concrete class for writing SVG output.
1274 def __init__(self, fileName):
1275 """Simply call the parent Contructor.
1277 VectorWriter.__init__(self, fileName)
1286 def open(self, startFrame=1, endFrame=1):
1287 """Do some initialization operations.
1289 VectorWriter.open(self, startFrame, endFrame)
1291 self.file = open(self.outputFileName, "w")
1296 """Do some finalization operation.
1303 # remember to call the close method of the parent as last
1304 VectorWriter.close(self)
1307 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1308 showHiddenEdges=False):
1309 """Convert the scene representation to SVG.
1312 Objects = scene.objects
1314 context = scene.getRenderingContext()
1315 framenumber = context.currentFrame()
1318 framestyle = "display:none"
1320 framestyle = "display:block"
1322 # Assign an id to this group so we can set properties on it using DOM
1323 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1324 (framenumber, framestyle) )
1329 if(obj.getType() != 'Mesh'):
1332 self.file.write("<g id=\"%s\">\n" % obj.getName())
1334 mesh = obj.getData(mesh=1)
1337 self._printPolygons(mesh)
1340 self._printEdges(mesh, showHiddenEdges)
1342 self.file.write("</g>\n")
1344 self.file.write("</g>\n")
1351 def _calcCanvasCoord(self, v):
1352 """Convert vertex in scene coordinates to canvas coordinates.
1355 pt = Vector([0, 0, 0])
1357 mW = float(self.canvasSize[0])/2.0
1358 mH = float(self.canvasSize[1])/2.0
1360 # rescale to canvas size
1361 pt[0] = v.co[0]*mW + mW
1362 pt[1] = v.co[1]*mH + mH
1365 # For now we want (0,0) in the top-left corner of the canvas.
1366 # Mirror and translate along y
1368 pt[1] += self.canvasSize[1]
1372 def _printHeader(self):
1373 """Print SVG header."""
1375 self.file.write("<?xml version=\"1.0\"?>\n")
1376 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1377 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1378 self.file.write("<svg version=\"1.0\"\n")
1379 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1380 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1384 delay = 1000/self.fps
1386 self.file.write("""\n<script type="text/javascript"><![CDATA[
1387 globalStartFrame=%d;
1390 timerID = setInterval("NextFrame()", %d);
1391 globalFrameCounter=%d;
1392 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1394 self.file.write("""\n
1395 function NextFrame()
1397 currentElement = document.getElementById('frame'+globalFrameCounter)
1398 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1400 if (!currentElement)
1405 if (globalFrameCounter > globalEndFrame)
1407 clearInterval(timerID)
1413 previousElement.style.display="none";
1415 currentElement.style.display="block";
1416 globalFrameCounter++;
1422 def _printFooter(self):
1423 """Print the SVG footer."""
1425 self.file.write("\n</svg>\n")
1427 def _printPolygons(self, mesh):
1428 """Print the selected (visible) polygons.
1431 if len(mesh.faces) == 0:
1434 self.file.write("<g>\n")
1436 for face in mesh.faces:
1440 self.file.write("<path d=\"")
1442 #p = self._calcCanvasCoord(face.verts[0])
1443 p = self._calcCanvasCoord(face.v[0])
1444 self.file.write("M %g,%g L " % (p[0], p[1]))
1446 for v in face.v[1:]:
1447 p = self._calcCanvasCoord(v)
1448 self.file.write("%g,%g " % (p[0], p[1]))
1450 # get rid of the last blank space, just cosmetics here.
1451 self.file.seek(-1, 1)
1452 self.file.write(" z\"\n")
1454 # take as face color the first vertex color
1457 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1459 color = [255, 255, 255, 255]
1461 # Convert the color to the #RRGGBB form
1462 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1464 # Handle transparent polygons
1467 opacity = float(color[3])/255.0
1468 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1469 #opacity_string = "opacity: %g;" % (opacity)
1471 self.file.write("\tstyle=\"fill:" + str_col + ";")
1472 self.file.write(opacity_string)
1474 # use the stroke property to alleviate the "adjacent edges" problem,
1475 # we simulate polygon expansion using borders,
1476 # see http://www.antigrain.com/svg/index.html for more info
1479 # EXPANSION TRICK is not that useful where there is transparency
1480 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1481 # str_col = "#000000" # For debug
1482 self.file.write(" stroke:%s;\n" % str_col)
1483 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1484 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1486 self.file.write("\"/>\n")
1488 self.file.write("</g>\n")
1490 def _printEdges(self, mesh, showHiddenEdges=False):
1491 """Print the wireframe using mesh edges.
1494 stroke_width = config.edges['WIDTH']
1495 stroke_col = config.edges['COLOR']
1497 self.file.write("<g>\n")
1499 for e in mesh.edges:
1501 hidden_stroke_style = ""
1504 if showHiddenEdges == False:
1507 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1509 p1 = self._calcCanvasCoord(e.v1)
1510 p2 = self._calcCanvasCoord(e.v2)
1512 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1513 % ( p1[0], p1[1], p2[0], p2[1] ) )
1514 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1515 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1516 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1517 self.file.write(hidden_stroke_style)
1518 self.file.write("\"/>\n")
1520 self.file.write("</g>\n")
1529 SWFSupported = False
1531 class SWFVectorWriter(VectorWriter):
1532 """A concrete class for writing SWF output.
1535 def __init__(self, fileName):
1536 """Simply call the parent Contructor.
1538 VectorWriter.__init__(self, fileName)
1548 def open(self, startFrame=1, endFrame=1):
1549 """Do some initialization operations.
1551 VectorWriter.open(self, startFrame, endFrame)
1552 self.movie = SWFMovie()
1553 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1555 self.movie.setRate(self.fps)
1556 numframes = endFrame - startFrame + 1
1557 self.movie.setFrames(numframes)
1560 """Do some finalization operation.
1562 self.movie.save(self.outputFileName)
1564 # remember to call the close method of the parent
1565 VectorWriter.close(self)
1567 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1568 showHiddenEdges=False):
1569 """Convert the scene representation to SVG.
1571 context = scene.getRenderingContext()
1572 framenumber = context.currentFrame()
1574 Objects = scene.objects
1577 self.movie.remove(self.sprite)
1579 sprite = SWFSprite()
1583 if(obj.getType() != 'Mesh'):
1586 mesh = obj.getData(mesh=1)
1589 self._printPolygons(mesh, sprite)
1592 self._printEdges(mesh, sprite, showHiddenEdges)
1595 i = self.movie.add(sprite)
1596 # Remove the instance the next time
1599 self.movie.nextFrame()
1606 def _calcCanvasCoord(self, v):
1607 """Convert vertex in scene coordinates to canvas coordinates.
1610 pt = Vector([0, 0, 0])
1612 mW = float(self.canvasSize[0])/2.0
1613 mH = float(self.canvasSize[1])/2.0
1615 # rescale to canvas size
1616 pt[0] = v.co[0]*mW + mW
1617 pt[1] = v.co[1]*mH + mH
1620 # For now we want (0,0) in the top-left corner of the canvas.
1621 # Mirror and translate along y
1623 pt[1] += self.canvasSize[1]
1627 def _printPolygons(self, mesh, sprite):
1628 """Print the selected (visible) polygons.
1631 if len(mesh.faces) == 0:
1634 for face in mesh.faces:
1640 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1642 color = [255, 255, 255, 255]
1645 f = s.addFill(color[0], color[1], color[2], color[3])
1648 # The starting point of the shape
1649 p0 = self._calcCanvasCoord(face.verts[0])
1650 s.movePenTo(p0[0], p0[1])
1652 for v in face.verts[1:]:
1653 p = self._calcCanvasCoord(v)
1654 s.drawLineTo(p[0], p[1])
1657 s.drawLineTo(p0[0], p0[1])
1663 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1664 """Print the wireframe using mesh edges.
1667 stroke_width = config.edges['WIDTH']
1668 stroke_col = config.edges['COLOR']
1672 for e in mesh.edges:
1674 # Next, we set the line width and color for our shape.
1675 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1679 if showHiddenEdges == False:
1682 # SWF does not support dashed lines natively, so -for now-
1683 # draw hidden lines thinner and half-trasparent
1684 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1687 p1 = self._calcCanvasCoord(e.v1)
1688 p2 = self._calcCanvasCoord(e.v2)
1690 s.movePenTo(p1[0], p1[1])
1691 s.drawLineTo(p2[0], p2[1])
1700 from reportlab.pdfgen import canvas
1703 PDFSupported = False
1705 class PDFVectorWriter(VectorWriter):
1706 """A concrete class for writing PDF output.
1709 def __init__(self, fileName):
1710 """Simply call the parent Contructor.
1712 VectorWriter.__init__(self, fileName)
1721 def open(self, startFrame=1, endFrame=1):
1722 """Do some initialization operations.
1724 VectorWriter.open(self, startFrame, endFrame)
1725 size = (self.canvasSize[0], self.canvasSize[1])
1726 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1729 """Do some finalization operation.
1733 # remember to call the close method of the parent
1734 VectorWriter.close(self)
1736 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1737 showHiddenEdges=False):
1738 """Convert the scene representation to SVG.
1740 context = scene.getRenderingContext()
1741 framenumber = context.currentFrame()
1743 Objects = scene.objects
1747 if(obj.getType() != 'Mesh'):
1750 mesh = obj.getData(mesh=1)
1753 self._printPolygons(mesh)
1756 self._printEdges(mesh, showHiddenEdges)
1758 self.canvas.showPage()
1764 def _calcCanvasCoord(self, v):
1765 """Convert vertex in scene coordinates to canvas coordinates.
1768 pt = Vector([0, 0, 0])
1770 mW = float(self.canvasSize[0])/2.0
1771 mH = float(self.canvasSize[1])/2.0
1773 # rescale to canvas size
1774 pt[0] = v.co[0]*mW + mW
1775 pt[1] = v.co[1]*mH + mH
1778 # For now we want (0,0) in the top-left corner of the canvas.
1779 # Mirror and translate along y
1781 pt[1] += self.canvasSize[1]
1785 def _printPolygons(self, mesh):
1786 """Print the selected (visible) polygons.
1789 if len(mesh.faces) == 0:
1792 for face in mesh.faces:
1798 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1801 color = [1, 1, 1, 1]
1803 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1805 self.canvas.setStrokeColorRGB(0, 0, 0)
1807 path = self.canvas.beginPath()
1809 # The starting point of the path
1810 p0 = self._calcCanvasCoord(face.verts[0])
1811 path.moveTo(p0[0], p0[1])
1813 for v in face.verts[1:]:
1814 p = self._calcCanvasCoord(v)
1815 path.lineTo(p[0], p[1])
1820 self.canvas.drawPath(path, stroke=0, fill=1)
1822 def _printEdges(self, mesh, showHiddenEdges=False):
1823 """Print the wireframe using mesh edges.
1826 stroke_width = config.edges['WIDTH']
1827 stroke_col = config.edges['COLOR']
1829 self.canvas.setLineCap(1)
1830 self.canvas.setLineJoin(1)
1831 self.canvas.setLineWidth(stroke_width)
1832 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1835 for e in mesh.edges:
1837 self.canvas.setLineWidth(stroke_width)
1840 if showHiddenEdges == False:
1843 # PDF does not support dashed lines natively, so -for now-
1844 # draw hidden lines thinner
1845 self.canvas.setLineWidth(stroke_width/2.0)
1847 p1 = self._calcCanvasCoord(e.v1)
1848 p2 = self._calcCanvasCoord(e.v2)
1850 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1854 # ---------------------------------------------------------------------
1856 ## Rendering Classes
1858 # ---------------------------------------------------------------------
1860 # A dictionary to collect different shading style methods
1861 shadingStyles = dict()
1862 shadingStyles['FLAT'] = None
1863 shadingStyles['TOON'] = None
1865 # A dictionary to collect different edge style methods
1867 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1868 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1870 # A dictionary to collect the supported output formats
1871 outputWriters = dict()
1872 outputWriters['SVG'] = SVGVectorWriter
1874 outputWriters['SWF'] = SWFVectorWriter
1876 outputWriters['PDF'] = PDFVectorWriter
1880 """Render a scene viewed from the active camera.
1882 This class is responsible of the rendering process, transformation and
1883 projection of the objects in the scene are invoked by the renderer.
1885 The rendering is done using the active camera for the current scene.
1889 """Make the rendering process only for the current scene by default.
1891 We will work on a copy of the scene, to be sure that the current scene do
1892 not get modified in any way.
1895 # Render the current Scene, this should be a READ-ONLY property
1896 self._SCENE = Scene.GetCurrent()
1898 # Use the aspect ratio of the scene rendering context
1899 context = self._SCENE.getRenderingContext()
1901 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1902 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1903 float(context.aspectRatioY())
1906 # Render from the currently active camera
1907 #self.cameraObj = self._SCENE.objects.camera
1916 def doRendering(self, outputWriter, animation=False):
1917 """Render picture or animation and write it out.
1920 - a Vector writer object that will be used to output the result.
1921 - a flag to tell if we want to render an animation or only the
1925 context = self._SCENE.getRenderingContext()
1926 origCurrentFrame = context.currentFrame()
1928 # Handle the animation case
1930 startFrame = origCurrentFrame
1931 endFrame = startFrame
1934 startFrame = context.startFrame()
1935 endFrame = context.endFrame()
1936 outputWriter.open(startFrame, endFrame)
1938 # Do the rendering process frame by frame
1939 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1940 for f in xrange(startFrame, endFrame+1):
1941 print "\n\nFrame: %d" % f
1943 # FIXME To get the correct camera position we have to use +1 here.
1944 # Is there a bug somewhere in the Scene module?
1945 context.currentFrame(f+1)
1946 self.cameraObj = self._SCENE.objects.camera
1948 # Use some temporary workspace, a full copy of the scene
1949 inputScene = self._SCENE.copy(2)
1951 # To get the objects at this frame remove the +1 ...
1952 ctx = inputScene.getRenderingContext()
1956 # Get a projector for this camera.
1957 # NOTE: the projector wants object in world coordinates,
1958 # so we should remember to apply modelview transformations
1959 # _before_ we do projection transformations.
1960 self.proj = Projector(self.cameraObj, self.canvasRatio)
1963 renderedScene = self.doRenderScene(inputScene)
1965 print "There was an error! Aborting."
1967 print traceback.print_exc()
1969 self._SCENE.makeCurrent()
1970 Scene.Unlink(inputScene)
1974 outputWriter.printCanvas(renderedScene,
1975 doPrintPolygons = config.polygons['SHOW'],
1976 doPrintEdges = config.edges['SHOW'],
1977 showHiddenEdges = config.edges['SHOW_HIDDEN'])
1979 # delete the rendered scene
1980 self._SCENE.makeCurrent()
1981 Scene.Unlink(renderedScene)
1984 outputWriter.close()
1986 context.currentFrame(origCurrentFrame)
1989 def doRenderScene(self, workScene):
1990 """Control the rendering process.
1992 Here we control the entire rendering process invoking the operation
1993 needed to transform and project the 3D scene in two dimensions.
1996 # global processing of the scene
1998 self._filterHiddenObjects(workScene)
2000 self._buildLightSetup(workScene)
2002 self._doSceneClipping(workScene)
2004 self._doConvertGeometricObjsToMesh(workScene)
2006 if config.output['JOIN_OBJECTS']:
2007 self._joinMeshObjectsInScene(workScene)
2009 self._doSceneDepthSorting(workScene)
2011 # Per object activities
2013 Objects = workScene.objects
2015 print "Total Objects: %d" % len(Objects)
2016 for i,obj in enumerate(Objects):
2018 print "Rendering Object: %d" % i
2020 if obj.getType() != 'Mesh':
2021 print "Only Mesh supported! - Skipping type:", obj.getType()
2024 print "Rendering: ", obj.getName()
2026 mesh = obj.getData(mesh=1)
2028 self._doModelingTransformation(mesh, obj.matrix)
2030 self._doBackFaceCulling(mesh)
2033 # When doing HSR with NEWELL we may want to flip all normals
2035 if config.polygons['HSR'] == "NEWELL":
2036 for f in mesh.faces:
2039 for f in mesh.faces:
2042 self._doLighting(mesh)
2044 # Do "projection" now so we perform further processing
2045 # in Normalized View Coordinates
2046 self._doProjection(mesh, self.proj)
2048 self._doViewFrustumClipping(mesh)
2050 self._doHiddenSurfaceRemoval(mesh)
2052 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2054 # Update the object data, important! :)
2066 def _getObjPosition(self, obj):
2067 """Return the obj position in World coordinates.
2069 return obj.matrix.translationPart()
2071 def _cameraViewVector(self):
2072 """Get the View Direction form the camera matrix.
2074 return Vector(self.cameraObj.matrix[2]).resize3D()
2079 def _isFaceVisible(self, face):
2080 """Determine if a face of an object is visible from the current camera.
2082 The view vector is calculated from the camera location and one of the
2083 vertices of the face (expressed in World coordinates, after applying
2084 modelview transformations).
2086 After those transformations we determine if a face is visible by
2087 computing the angle between the face normal and the view vector, this
2088 angle has to be between -90 and 90 degrees for the face to be visible.
2089 This corresponds somehow to the dot product between the two, if it
2090 results > 0 then the face is visible.
2092 There is no need to normalize those vectors since we are only interested in
2093 the sign of the cross product and not in the product value.
2095 NOTE: here we assume the face vertices are in WorldCoordinates, so
2096 please transform the object _before_ doing the test.
2099 normal = Vector(face.no)
2100 camPos = self._getObjPosition(self.cameraObj)
2103 # View Vector in orthographics projections is the view Direction of
2105 if self.cameraObj.data.getType() == 1:
2106 view_vect = self._cameraViewVector()
2108 # View vector in perspective projections can be considered as
2109 # the difference between the camera position and one point of
2110 # the face, we choose the farthest point from the camera.
2111 if self.cameraObj.data.getType() == 0:
2112 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2116 # if d > 0 the face is visible from the camera
2117 d = view_vect * normal
2127 def _filterHiddenObjects(self, scene):
2128 """Discard object that are on hidden layers in the scene.
2131 Objects = scene.objects
2133 visible_obj_list = [ obj for obj in Objects if
2134 set(obj.layers).intersection(set(scene.getLayers())) ]
2137 if o not in visible_obj_list:
2138 scene.objects.unlink(o)
2144 def _buildLightSetup(self, scene):
2145 # Get the list of lighting sources
2146 obj_lst = scene.objects
2147 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp' ]
2149 # When there are no lights we use a default lighting source
2150 # that have the same position of the camera
2151 if len(self.lights) == 0:
2152 l = Lamp.New('Lamp')
2153 lobj = Object.New('Lamp')
2154 lobj.loc = self.cameraObj.loc
2156 self.lights.append(lobj)
2159 def _doSceneClipping(self, scene):
2160 """Clip whole objects against the View Frustum.
2162 For now clip away only objects according to their center position.
2165 cam_pos = self._getObjPosition(self.cameraObj)
2166 view_vect = self._cameraViewVector()
2168 near = self.cameraObj.data.clipStart
2169 far = self.cameraObj.data.clipEnd
2171 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2172 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2173 fovy = fovy * 360.0/pi
2175 Objects = scene.objects
2178 if o.getType() != 'Mesh': continue;
2181 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2183 d = obj_vect*view_vect
2184 theta = AngleBetweenVecs(obj_vect, view_vect)
2186 # if the object is outside the view frustum, clip it away
2187 if (d < near) or (d > far) or (theta > fovy):
2188 scene.objects.unlink(o)
2191 # Use the object bounding box
2192 # (whose points are already in WorldSpace Coordinate)
2194 bb = o.getBoundBox()
2198 p_vect = Vector(cam_pos) - Vector(p)
2200 d = p_vect * view_vect
2201 theta = AngleBetweenVecs(p_vect, view_vect)
2203 # Is this point outside the view frustum?
2204 if (d < near) or (d > far) or (theta > fovy):
2207 # If the bb is all outside the view frustum we clip the whole
2209 if points_outside == len(bb):
2210 scene.objects.unlink(o)
2214 def _doConvertGeometricObjsToMesh(self, scene):
2215 """Convert all "geometric" objects to mesh ones.
2217 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2218 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2220 Objects = scene.objects
2222 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2225 obj = self._convertToRawMeshObj(obj)
2226 scene.objects.link(obj)
2227 scene.objects.unlink(old_obj)
2230 # XXX Workaround for Text and Curve which have some normals
2231 # inverted when they are converted to Mesh, REMOVE that when
2232 # blender will fix that!!
2233 if old_obj.getType() in ['Curve', 'Text']:
2234 me = obj.getData(mesh=1)
2235 for f in me.faces: f.sel = 1;
2236 for v in me.verts: v.sel = 1;
2243 def _doSceneDepthSorting(self, scene):
2244 """Sort objects in the scene.
2246 The object sorting is done accordingly to the object centers.
2249 c = self._getObjPosition(self.cameraObj)
2251 by_obj_center_pos = (lambda o1, o2:
2252 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2253 cmp((self._getObjPosition(o1) - Vector(c)).length,
2254 (self._getObjPosition(o2) - Vector(c)).length)
2257 # Implement sorting by bounding box, the object with the bb
2258 # nearest to the camera should be drawn as last.
2259 by_nearest_bbox_point = (lambda o1, o2:
2260 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2261 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2262 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2267 Objects = list(scene.objects)
2269 #Objects.sort(by_obj_center_pos)
2270 Objects.sort(by_nearest_bbox_point)
2274 scene.objects.unlink(o)
2275 scene.objects.link(o)
2277 def _joinMeshObjectsInScene(self, scene):
2278 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2281 oList = [o for o in scene.objects if o.getType()=='Mesh']
2283 # FIXME: Object.join() do not work if the list contains 1 object
2287 mesh = Mesh.New('BigOne')
2288 bigObj = Object.New('Mesh', 'BigOne')
2291 scene.objects.link(bigObj)
2295 except RuntimeError:
2296 print "\nWarning! - Can't Join Objects\n"
2297 scene.objects.unlink(bigObj)
2300 print "Objects Type error?"
2303 scene.objects.unlink(o)
2308 # Per object/mesh methods
2310 def _convertToRawMeshObj(self, object):
2311 """Convert geometry based object to a mesh object.
2313 me = Mesh.New('RawMesh_'+object.name)
2314 me.getFromObject(object.name)
2316 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2319 # If the object has no materials set a default material
2320 if not me.materials:
2321 me.materials = [Material.New()]
2322 #for f in me.faces: f.mat = 0
2324 newObject.setMatrix(object.getMatrix())
2328 def _doModelingTransformation(self, mesh, matrix):
2329 """Transform object coordinates to world coordinates.
2331 This step is done simply applying to the object its tranformation
2332 matrix and recalculating its normals.
2334 # XXX FIXME: blender do not transform normals in the right way when
2335 # there are negative scale values
2336 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2337 print "WARNING: Negative scales, expect incorrect results!"
2339 mesh.transform(matrix, True)
2341 def _doBackFaceCulling(self, mesh):
2342 """Simple Backface Culling routine.
2344 At this level we simply do a visibility test face by face and then
2345 select the vertices belonging to visible faces.
2348 # Select all vertices, so edges can be displayed even if there are no
2350 for v in mesh.verts:
2353 Mesh.Mode(Mesh.SelectModes['FACE'])
2355 for f in mesh.faces:
2357 if self._isFaceVisible(f):
2360 def _doLighting(self, mesh):
2361 """Apply an Illumination and shading model to the object.
2363 The model used is the Phong one, it may be inefficient,
2364 but I'm just learning about rendering and starting from Phong seemed
2365 the most natural way.
2368 # If the mesh has vertex colors already, use them,
2369 # otherwise turn them on and do some calculations
2370 if mesh.vertexColors:
2372 mesh.vertexColors = 1
2374 materials = mesh.materials
2376 camPos = self._getObjPosition(self.cameraObj)
2378 # We do per-face color calculation (FLAT Shading), we can easily turn
2379 # to a per-vertex calculation if we want to implement some shading
2380 # technique. For an example see:
2381 # http://www.miralab.unige.ch/papers/368.pdf
2382 for f in mesh.faces:
2388 mat = materials[f.mat]
2390 # A new default material
2392 mat = Material.New('defMat')
2394 # Check if it is a shadeless material
2395 elif mat.getMode() & Material.Modes['SHADELESS']:
2397 # Convert to a value between 0 and 255
2398 tmp_col = [ int(c * 255.0) for c in I]
2409 # do vertex color calculation
2411 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2413 for l in self.lights:
2415 light_pos = self._getObjPosition(l)
2416 light = light_obj.getData()
2418 L = Vector(light_pos).normalize()
2420 V = (Vector(camPos) - Vector(f.cent)).normalize()
2422 N = Vector(f.no).normalize()
2424 if config.polygons['SHADING'] == 'TOON':
2425 NL = ShadingUtils.toonShading(N*L)
2429 # Should we use NL instead of (N*L) here?
2430 R = 2 * (N*L) * N - L
2432 Ip = light.getEnergy()
2434 # Diffuse co-efficient
2435 kd = mat.getRef() * Vector(mat.getRGBCol())
2437 kd[i] *= light.col[i]
2439 Idiff = Ip * kd * max(0, NL)
2442 # Specular component
2443 ks = mat.getSpec() * Vector(mat.getSpecCol())
2444 ns = mat.getHardness()
2445 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2447 TotDiffSpec += (Idiff+Ispec)
2451 Iamb = Vector(Blender.World.Get()[0].getAmb())
2454 # Emissive component (convert to a triplet)
2455 ki = Vector([mat.getEmit()]*3)
2457 #I = ki + Iamb + (Idiff + Ispec)
2458 I = ki + (ka * Iamb) + TotDiffSpec
2461 # Set Alpha component
2463 I.append(mat.getAlpha())
2465 # Clamp I values between 0 and 1
2466 I = [ min(c, 1) for c in I]
2467 I = [ max(0, c) for c in I]
2469 # Convert to a value between 0 and 255
2470 tmp_col = [ int(c * 255.0) for c in I]
2478 def _doProjection(self, mesh, projector):
2479 """Apply Viewing and Projection tranformations.
2482 for v in mesh.verts:
2483 p = projector.doProjection(v.co[:])
2488 #mesh.recalcNormals()
2491 # We could reeset Camera matrix, since now
2492 # we are in Normalized Viewing Coordinates,
2493 # but doung that would affect World Coordinate
2494 # processing for other objects
2496 #self.cameraObj.data.type = 1
2497 #self.cameraObj.data.scale = 2.0
2498 #m = Matrix().identity()
2499 #self.cameraObj.setMatrix(m)
2501 def _doViewFrustumClipping(self, mesh):
2502 """Clip faces against the View Frustum.
2505 # The Canonical View Volume, 8 vertices, and 6 faces,
2506 # We consider its face normals pointing outside
2508 v1 = NMesh.Vert(1, 1, -1)
2509 v2 = NMesh.Vert(1, -1, -1)
2510 v3 = NMesh.Vert(-1, -1, -1)
2511 v4 = NMesh.Vert(-1, 1, -1)
2512 v5 = NMesh.Vert(1, 1, 1)
2513 v6 = NMesh.Vert(1, -1, 1)
2514 v7 = NMesh.Vert(-1, -1, 1)
2515 v8 = NMesh.Vert(-1, 1, 1)
2518 f1 = NMesh.Face([v1, v4, v3, v2])
2520 f2 = NMesh.Face([v5, v6, v7, v8])
2522 f3 = NMesh.Face([v1, v2, v6, v5])
2524 f4 = NMesh.Face([v2, v3, v7, v6])
2526 f5 = NMesh.Face([v3, v4, v8, v7])
2528 f6 = NMesh.Face([v4, v1, v5, v8])
2531 nmesh = NMesh.GetRaw(mesh.name)
2532 clippedfaces = nmesh.faces[:]
2533 facelist = clippedfaces[:]
2535 for clipface in cvv:
2541 #newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2545 # Check if the face is all outside the view frustum
2546 # TODO: Do this test before, it is more efficient
2549 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2552 if points_outside != len(f):
2553 clippedfaces.append(f)
2557 nmesh.verts.append(v)
2561 nf.col = [f.col[0]] * len(nf.v)
2563 clippedfaces.append(nf)
2564 facelist = clippedfaces[:]
2567 nmesh.faces = facelist
2572 def __simpleDepthSort(self, mesh):
2573 """Sort faces by the furthest vertex.
2575 This simple mesthod is known also as the painter algorithm, and it
2576 solves HSR correctly only for convex meshes.
2581 # The sorting requires circa n*log(n) steps
2583 progress.setActivity("HSR: Painter", n*log(n))
2585 by_furthest_z = (lambda f1, f2: progress.update() and
2586 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2589 # FIXME: using NMesh to sort faces. We should avoid that!
2590 nmesh = NMesh.GetRaw(mesh.name)
2592 # remember that _higher_ z values mean further points
2593 nmesh.faces.sort(by_furthest_z)
2594 nmesh.faces.reverse()
2599 def __newellDepthSort(self, mesh):
2600 """Newell's depth sorting.
2606 # Find non planar quads and convert them to triangle
2607 #for f in mesh.faces:
2609 # if is_nonplanar_quad(f.v):
2610 # print "NON QUAD??"
2614 # Now reselect all faces
2615 for f in mesh.faces:
2617 mesh.quadToTriangle()
2619 # FIXME: using NMesh to sort faces. We should avoid that!
2620 nmesh = NMesh.GetRaw(mesh.name)
2622 # remember that _higher_ z values mean further points
2623 nmesh.faces.sort(by_furthest_z)
2624 nmesh.faces.reverse()
2626 # Begin depth sort tests
2628 # use the smooth flag to set marked faces
2629 for f in nmesh.faces:
2632 facelist = nmesh.faces[:]
2636 # The steps are _at_least_ equal to len(facelist), we do not count the
2637 # feces coming out from splitting!!
2638 progress.setActivity("HSR: Newell", len(facelist))
2639 #progress.setQuiet(True)
2642 while len(facelist):
2643 debug("\n----------------------\n")
2644 debug("len(facelits): %d\n" % len(facelist))
2647 pSign = sign(P.normal[2])
2649 # We can discard faces parallel to the view vector
2650 #if P.normal[2] == 0:
2651 # facelist.remove(P)
2657 for Q in facelist[1:]:
2659 debug("P.smooth: " + str(P.smooth) + "\n")
2660 debug("Q.smooth: " + str(Q.smooth) + "\n")
2663 qSign = sign(Q.normal[2])
2664 # TODO: check also if Q is parallel??
2666 # Test 0: We need to test only those Qs whose furthest vertex
2667 # is closer to the observer than the closest vertex of P.
2669 zP = [v.co[2] for v in P.v]
2670 zQ = [v.co[2] for v in Q.v]
2671 notZOverlap = min(zP) > max(zQ) + EPS
2675 debug("NOT Z OVERLAP!\n")
2677 # If Q is not marked then we can safely print P
2680 debug("met a marked face\n")
2684 # Test 1: X extent overlapping
2685 xP = [v.co[0] for v in P.v]
2686 xQ = [v.co[0] for v in Q.v]
2687 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2688 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2692 debug("NOT X OVERLAP!\n")
2696 # Test 2: Y extent Overlapping
2697 yP = [v.co[1] for v in P.v]
2698 yQ = [v.co[1] for v in Q.v]
2699 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2700 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2704 debug("NOT Y OVERLAP!\n")
2708 # Test 3: P vertices are all behind the plane of Q
2711 d = qSign * HSR.Distance(Vector(Pi), Q)
2714 pVerticesBehindPlaneQ = (n == len(P))
2716 if pVerticesBehindPlaneQ:
2718 debug("P BEHIND Q!\n")
2722 # Test 4: Q vertices in front of the plane of P
2725 d = pSign * HSR.Distance(Vector(Qi), P)
2728 qVerticesInFrontPlaneP = (n == len(Q))
2730 if qVerticesInFrontPlaneP:
2732 debug("Q IN FRONT OF P!\n")
2736 # Test 5: Check if projections of polygons effectively overlap,
2737 # in previous tests we checked only bounding boxes.
2739 #if not projectionsOverlap(P, Q):
2740 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2742 debug("Projections do not overlap!\n")
2745 # We still can't say if P obscures Q.
2747 # But if Q is marked we do a face-split trying to resolve a
2748 # difficulty (maybe a visibility cycle).
2751 debug("Possibly a cycle detected!\n")
2752 debug("Split here!!\n")
2754 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2758 # The question now is: Does Q obscure P?
2761 # Test 3bis: Q vertices are all behind the plane of P
2764 d = pSign * HSR.Distance(Vector(Qi), P)
2767 qVerticesBehindPlaneP = (n == len(Q))
2769 if qVerticesBehindPlaneP:
2770 debug("\nTest 3bis\n")
2771 debug("Q BEHIND P!\n")
2774 # Test 4bis: P vertices in front of the plane of Q
2777 d = qSign * HSR.Distance(Vector(Pi), Q)
2780 pVerticesInFrontPlaneQ = (n == len(P))
2782 if pVerticesInFrontPlaneQ:
2783 debug("\nTest 4bis\n")
2784 debug("P IN FRONT OF Q!\n")
2787 # We don't even know if Q does obscure P, so they should
2788 # intersect each other, split one of them in two parts.
2789 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2790 debug("\nSimple Intersection?\n")
2791 debug("Test 3bis or 4bis failed\n")
2792 debug("Split here!!2\n")
2794 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2799 facelist.insert(0, Q)
2802 debug("Q marked!\n")
2806 if split_done == 0 and face_marked == 0:
2809 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2813 if len(facelist) == 870:
2814 dumpfaces([P, Q], "loopdebug.svg")
2817 #if facelist == None:
2819 # print [v.co for v in P]
2820 # print [v.co for v in Q]
2823 # end of while len(facelist)
2826 nmesh.faces = maplist
2827 #for f in nmesh.faces:
2833 def _doHiddenSurfaceRemoval(self, mesh):
2834 """Do HSR for the given mesh.
2836 if len(mesh.faces) == 0:
2839 if config.polygons['HSR'] == 'PAINTER':
2840 print "\nUsing the Painter algorithm for HSR."
2841 self.__simpleDepthSort(mesh)
2843 elif config.polygons['HSR'] == 'NEWELL':
2844 print "\nUsing the Newell's algorithm for HSR."
2845 self.__newellDepthSort(mesh)
2848 def _doEdgesStyle(self, mesh, edgestyleSelect):
2849 """Process Mesh Edges accroding to a given selection style.
2851 Examples of algorithms:
2854 given an edge if its adjacent faces have the same normal (that is
2855 they are complanar), than deselect it.
2858 given an edge if one its adjacent faces is frontfacing and the
2859 other is backfacing, than select it, else deselect.
2862 Mesh.Mode(Mesh.SelectModes['EDGE'])
2864 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2866 for i,edge_faces in enumerate(edge_cache):
2867 mesh.edges[i].sel = 0
2868 if edgestyleSelect(edge_faces):
2869 mesh.edges[i].sel = 1
2872 for e in mesh.edges:
2875 if edgestyleSelect(e, mesh):
2881 # ---------------------------------------------------------------------
2883 ## GUI Class and Main Program
2885 # ---------------------------------------------------------------------
2888 from Blender import BGL, Draw
2889 from Blender.BGL import *
2895 # Output Format menu
2896 output_format = config.output['FORMAT']
2897 default_value = outputWriters.keys().index(output_format)+1
2898 GUI.outFormatMenu = Draw.Create(default_value)
2899 GUI.evtOutFormatMenu = 0
2901 # Animation toggle button
2902 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2903 GUI.evtAnimToggle = 1
2905 # Join Objects toggle button
2906 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2907 GUI.evtJoinObjsToggle = 2
2909 # Render filled polygons
2910 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2912 # Shading Style menu
2913 shading_style = config.polygons['SHADING']
2914 default_value = shadingStyles.keys().index(shading_style)+1
2915 GUI.shadingStyleMenu = Draw.Create(default_value)
2916 GUI.evtShadingStyleMenu = 21
2918 GUI.evtPolygonsToggle = 3
2919 # We hide the config.polygons['EXPANSION_TRICK'], for now
2921 # Render polygon edges
2922 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2923 GUI.evtShowEdgesToggle = 4
2925 # Render hidden edges
2926 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2927 GUI.evtShowHiddenEdgesToggle = 5
2930 edge_style = config.edges['STYLE']
2931 default_value = edgeStyles.keys().index(edge_style)+1
2932 GUI.edgeStyleMenu = Draw.Create(default_value)
2933 GUI.evtEdgeStyleMenu = 6
2936 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2937 GUI.evtEdgeWidthSlider = 7
2940 c = config.edges['COLOR']
2941 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2942 GUI.evtEdgeColorPicker = 71
2945 GUI.evtRenderButton = 8
2948 GUI.evtExitButton = 9
2950 # Save default button
2951 GUI.evtSaveDefaultButton = 99
2955 # initialize static members
2958 glClear(GL_COLOR_BUFFER_BIT)
2959 glColor3f(0.0, 0.0, 0.0)
2960 glRasterPos2i(10, 380)
2961 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2963 glRasterPos2i(10, 365)
2964 Draw.Text("%s (c) 2006, 2007" % __author__)
2966 glRasterPos2i(10, 335)
2967 Draw.Text("Press Q or ESC to quit.")
2969 # Build the output format menu
2970 glRasterPos2i(10, 310)
2971 Draw.Text("Select the output Format:")
2972 outMenuStruct = "Output Format %t"
2973 for t in outputWriters.keys():
2974 outMenuStruct = outMenuStruct + "|%s" % t
2975 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2976 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2979 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2980 10, 260, 160, 18, GUI.animToggle.val,
2981 "Toggle rendering of animations")
2983 # Join Objects toggle
2984 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2985 10, 235, 160, 18, GUI.joinObjsToggle.val,
2986 "Join objects in the rendered file")
2989 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2991 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2993 Draw.Button("Save settings as default", GUI.evtSaveDefaultButton, 10, 210-50, 160, 18,
2994 "Save settings as default")
2997 glRasterPos2i(200, 310)
2998 Draw.Text("Rendering Style:")
3001 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
3002 200, 285, 160, 18, GUI.polygonsToggle.val,
3003 "Render filled polygons")
3005 if GUI.polygonsToggle.val == 1:
3007 # Polygon Shading Style
3008 shadingStyleMenuStruct = "Shading Style %t"
3009 for t in shadingStyles.keys():
3010 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
3011 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
3012 200, 260, 160, 18, GUI.shadingStyleMenu.val,
3013 "Choose the shading style")
3017 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
3018 200, 235, 160, 18, GUI.showEdgesToggle.val,
3019 "Render polygon edges")
3021 if GUI.showEdgesToggle.val == 1:
3024 edgeStyleMenuStruct = "Edge Style %t"
3025 for t in edgeStyles.keys():
3026 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
3027 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
3028 200, 210, 160, 18, GUI.edgeStyleMenu.val,
3029 "Choose the edge style")
3032 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
3033 200, 185, 140, 18, GUI.edgeWidthSlider.val,
3034 0.0, 10.0, 0, "Change Edge Width")
3037 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
3038 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
3041 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
3042 GUI.evtShowHiddenEdgesToggle,
3043 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
3044 "Render hidden edges as dashed lines")
3047 def event(evt, val):
3049 if evt == Draw.ESCKEY or evt == Draw.QKEY:
3056 def button_event(evt):
3058 if evt == GUI.evtExitButton:
3061 elif evt == GUI.evtOutFormatMenu:
3062 i = GUI.outFormatMenu.val - 1
3063 config.output['FORMAT']= outputWriters.keys()[i]
3064 # Set the new output file
3066 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3068 elif evt == GUI.evtAnimToggle:
3069 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3071 elif evt == GUI.evtJoinObjsToggle:
3072 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3074 elif evt == GUI.evtPolygonsToggle:
3075 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3077 elif evt == GUI.evtShadingStyleMenu:
3078 i = GUI.shadingStyleMenu.val - 1
3079 config.polygons['SHADING'] = shadingStyles.keys()[i]
3081 elif evt == GUI.evtShowEdgesToggle:
3082 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3084 elif evt == GUI.evtShowHiddenEdgesToggle:
3085 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3087 elif evt == GUI.evtEdgeStyleMenu:
3088 i = GUI.edgeStyleMenu.val - 1
3089 config.edges['STYLE'] = edgeStyles.keys()[i]
3091 elif evt == GUI.evtEdgeWidthSlider:
3092 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3094 elif evt == GUI.evtEdgeColorPicker:
3095 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3097 elif evt == GUI.evtRenderButton:
3098 label = "Save %s" % config.output['FORMAT']
3099 # Show the File Selector
3101 Blender.Window.FileSelector(vectorize, label, outputfile)
3103 elif evt == GUI.evtSaveDefaultButton:
3104 config.saveToRegistry()
3107 print "Event: %d not handled!" % evt
3114 from pprint import pprint
3116 pprint(config.output)
3117 pprint(config.polygons)
3118 pprint(config.edges)
3120 _init = staticmethod(_init)
3121 draw = staticmethod(draw)
3122 event = staticmethod(event)
3123 button_event = staticmethod(button_event)
3124 conf_debug = staticmethod(conf_debug)
3126 # A wrapper function for the vectorizing process
3127 def vectorize(filename):
3128 """The vectorizing process is as follows:
3130 - Instanciate the writer and the renderer
3135 print "\nERROR: invalid file name!"
3138 from Blender import Window
3139 editmode = Window.EditMode()
3140 if editmode: Window.EditMode(0)
3142 actualWriter = outputWriters[config.output['FORMAT']]
3143 writer = actualWriter(filename)
3145 renderer = Renderer()
3146 renderer.doRendering(writer, config.output['ANIMATION'])
3148 if editmode: Window.EditMode(1)
3153 if __name__ == "__main__":
3157 config.loadFromRegistry()
3159 # initialize writer setting also here to configure writer specific
3160 # settings on startup
3161 actualWriter = outputWriters[config.output['FORMAT']]
3162 writer = actualWriter("")
3165 basename = Blender.sys.basename(Blender.Get('filename'))
3167 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3169 if Blender.mode == 'background':
3170 progress = ConsoleProgressIndicator()
3171 vectorize(outputfile)
3173 progress = GraphicalProgressIndicator()
3174 Draw.Register(GUI.draw, GUI.event, GUI.button_event)