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, 2007, 2008 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 # ---------------------------------------------------------------------
68 # * Adapted to blender API 2.45
69 # * First release after code restucturing.
70 # Now the script offers a useful set of functionalities
71 # and it can render animations, too.
72 # * Optimization in Renderer.doEdgeStyle(), build a topology cache
73 # so to speed up the lookup of adjacent faces of an edge.
75 # * The SVG output is now SVG 1.0 valid.
76 # Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
77 # * Progress indicator during HSR.
78 # * Initial SWF output support (using ming)
79 # * Fixed a bug in the animation code, now the projection matrix is
80 # recalculated at each frame!
81 # * PDF output (using reportlab)
82 # * Fixed another problem in the animation code the current frame was off
83 # by one in the case of camera movement.
84 # * Use fps as specified in blender when VectorWriter handles animation
85 # * Remove the real file opening in the abstract VectorWriter
86 # * View frustum clipping
87 # * Scene clipping done using bounding box instead of object center
88 # * Fix camera type selection for blender>2.43 (Thanks to Thomas Lachmann)
89 # * Compatibility with python 2.3
90 # * Process only object that are on visible layers.
91 # * Saving config to registry (Thanks to Thomas Lachmann for a draft
94 # ---------------------------------------------------------------------
97 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
98 from Blender.Mathutils import *
105 from sets import Set as set
110 return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
111 # in python > 2.4 we ca use the following
112 #return [ u for u in alist if u not in locals()['_[1]'] ]
118 # We use a global progress Indicator Object
122 # Config class for global settings
126 polygons['SHOW'] = True
127 polygons['SHADING'] = 'FLAT' # FLAT or TOON
128 polygons['HSR'] = 'PAINTER' # PAINTER or NEWELL
129 # Hidden to the user for now
130 polygons['EXPANSION_TRICK'] = True
132 polygons['TOON_LEVELS'] = 2
135 edges['SHOW'] = False
136 edges['SHOW_HIDDEN'] = False
137 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
139 edges['COLOR'] = [0, 0, 0]
142 output['FORMAT'] = 'SVG'
143 output['ANIMATION'] = False
144 output['JOIN_OBJECTS'] = True
146 def saveToRegistry():
149 for k,v in config.__dict__.iteritems():
151 # config class store settings in dictionaries
152 if v.__class__ == dict().__class__:
154 regkey_prefix = k.upper()+"_"
156 for opt_k,opt_v in v.iteritems():
157 regkey = regkey_prefix + opt_k
159 registry[regkey] = opt_v
161 Blender.Registry.SetKey('VRM', registry, True)
163 saveToRegistry = staticmethod(saveToRegistry)
165 def loadFromRegistry():
166 registry = Blender.Registry.GetKey('VRM', True)
170 for k,v in registry.iteritems():
172 conf_attr = k_tmp[0].lower()
173 conf_key = str.join("_",k_tmp[1:])
176 if config.__dict__.has_key(conf_attr):
177 config.__dict__[conf_attr][conf_key] = conf_val
179 loadFromRegistry = staticmethod(loadFromRegistry)
185 def dumpfaces(flist, filename):
186 """Dump a single face to a file.
197 writerobj = SVGVectorWriter(filename)
200 writerobj._printPolygons(m)
206 sys.stderr.write(msg)
209 return (abs(v1[0]-v2[0]) < EPS and
210 abs(v1[1]-v2[1]) < EPS )
211 by_furthest_z = (lambda f1, f2:
212 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
227 # ---------------------------------------------------------------------
231 # ---------------------------------------------------------------------
237 """A utility class for HSR processing.
240 def is_nonplanar_quad(face):
241 """Determine if a quad is non-planar.
243 From: http://mathworld.wolfram.com/Coplanar.html
245 Geometric objects lying in a common plane are said to be coplanar.
246 Three noncollinear points determine a plane and so are trivially coplanar.
247 Four points are coplanar iff the volume of the tetrahedron defined by them is
253 | x_4 y_4 z_4 1 | == 0
255 Coplanarity is equivalent to the statement that the pair of lines
256 determined by the four points are not skew, and can be equivalently stated
257 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
259 An arbitrary number of n points x_1, ..., x_n can be tested for
260 coplanarity by finding the point-plane distances of the points
261 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
262 and checking if they are all zero.
263 If so, the points are all coplanar.
265 We here check only for 4-point complanarity.
271 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
275 # three points must be complanar
278 x1 = Vector(face[0].co)
279 x2 = Vector(face[1].co)
280 x3 = Vector(face[2].co)
281 x4 = Vector(face[3].co)
283 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
289 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
291 def pointInPolygon(poly, v):
294 pointInPolygon = staticmethod(pointInPolygon)
296 def edgeIntersection(s1, s2, do_perturbate=False):
298 (x1, y1) = s1[0].co[0], s1[0].co[1]
299 (x2, y2) = s1[1].co[0], s1[1].co[1]
301 (x3, y3) = s2[0].co[0], s2[0].co[1]
302 (x4, y4) = s2[1].co[0], s2[1].co[1]
310 # calculate delta values (vector components)
319 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
320 if C == 0: #/* parallel */
323 dx3 = x1 - x3 # /* combined origin offset vector */
326 a1 = (dy3 * dx2 - dx3 * dy2) / C;
327 a2 = (dy3 * dx1 - dx3 * dy1) / C;
329 # check for degeneracies
331 #print_debug(str(a1)+"\n")
332 #print_debug(str(a2)+"\n\n")
334 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
335 # Intersection on boundaries, we consider the point external?
338 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
344 return (NMesh.Vert(x, y, z), a1, a2)
347 # lines have intersections but not those segments
350 edgeIntersection = staticmethod(edgeIntersection)
352 def isVertInside(self, v):
356 # Create point at infinity
357 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
359 for i in range(len(self.v)):
360 s1 = (point_at_infinity, v)
361 s2 = (self.v[i-1], self.v[i])
363 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
366 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
370 if winding_number % 2 == 0 :
377 isVertInside = staticmethod(isVertInside)
381 return ((b[0] - a[0]) * (c[1] - a[1]) -
382 (b[1] - a[1]) * (c[0] - a[0]) )
384 det = staticmethod(det)
386 def pointInPolygon(q, P):
389 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
393 for i in range(len(P.v)):
396 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
397 if det(p0.co, p1.co, q.co) == 0 :
400 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
405 pointInPolygon = staticmethod(pointInPolygon)
407 def projectionsOverlap(f1, f2):
408 """ If you have nonconvex, but still simple polygons, an acceptable method
409 is to iterate over all vertices and perform the Point-in-polygon test[1].
410 The advantage of this method is that you can compute the exact
411 intersection point and collision normal that you will need to simulate
412 collision. When you have the point that lies inside the other polygon, you
413 just iterate over all edges of the second polygon again and look for edge
414 intersections. Note that this method detects collsion when it already
415 happens. This algorithm is fast enough to perform it hundreds of times per
418 for i in range(len(f1.v)):
421 # If a point of f1 in inside f2, there is an overlap!
423 #if HSR.isVertInside(f2, v1):
424 if HSR.pointInPolygon(v1, f2):
427 # If not the polygon can be ovelap as well, so we check for
428 # intersection between an edge of f1 and all the edges of f2
432 for j in range(len(f2.v)):
439 intrs = HSR.edgeIntersection(e1, e2)
441 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
442 # str(v2.co) + " " + str(v3.co) )
443 #print_debug("\nIntersection\n")
449 projectionsOverlap = staticmethod(projectionsOverlap)
451 def midpoint(p1, p2):
452 """Return the midpoint of two vertices.
454 m = MidpointVecs(Vector(p1), Vector(p2))
455 mv = NMesh.Vert(m[0], m[1], m[2])
459 midpoint = staticmethod(midpoint)
461 def facesplit(P, Q, facelist, nmesh):
462 """Split P or Q according to the strategy illustrated in the Newell's
466 by_furthest_z = (lambda f1, f2:
467 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
470 # Choose if split P on Q plane or vice-versa
474 d = HSR.Distance(Vector(Pi), Q)
477 pIntersectQ = (n != len(P))
481 d = HSR.Distance(Vector(Qi), P)
484 qIntersectP = (n != len(Q))
488 # 1. If parts of P lie in both half-spaces of Q
489 # then splice P in two with the plane of Q
495 newfaces = HSR.splitOn(plane, f)
497 # 2. Else if parts of Q lie in both half-space of P
498 # then splice Q in two with the plane of P
499 if qIntersectP and newfaces == None:
504 newfaces = HSR.splitOn(plane, f)
507 # 3. Else slice P in half through the mid-point of
508 # the longest pair of opposite sides
511 print "We ignore P..."
518 # v1 = midpoint(f[0], f[1])
519 # v2 = midpoint(f[1], f[2])
521 # v1 = midpoint(f[0], f[1])
522 # v2 = midpoint(f[2], f[3])
523 #vec3 = (Vector(v2)+10*Vector(f.normal))
525 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
527 #plane = NMesh.Face([v1, v2, v3])
529 #newfaces = splitOn(plane, f)
533 print "Big FAT problem, we weren't able to split POLYGONS!"
539 # if v not in plane and v in nmesh.verts:
540 # nmesh.verts.remove(v)
545 nf.col = [f.col[0]] * len(nf.v)
550 nmesh.verts.append(v)
551 # insert pieces in the list
556 # and resort the faces
557 facelist.sort(by_furthest_z)
558 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
561 #print [ f.smooth for f in facelist ]
565 facesplit = staticmethod(facesplit)
567 def isOnSegment(v1, v2, p, extremes_internal=False):
568 """Check if point p is in segment v1v2.
574 # Should we consider extreme points as internal ?
576 # if p == v1 or p == v2:
577 if l1 < EPS or l2 < EPS:
578 return extremes_internal
582 # if the sum of l1 and l2 is circa l, then the point is on segment,
583 if abs(l - (l1+l2)) < EPS:
588 isOnSegment = staticmethod(isOnSegment)
590 def Distance(point, face):
591 """ Calculate the distance between a point and a face.
593 An alternative but more expensive method can be:
595 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
596 Vector(face.no), Vector(point), 0)
598 d = Vector(ip - point).length
600 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
604 plNormal = Vector(face.no)
605 plVert0 = Vector(face.v[0])
607 d = (plVert0 * plNormal) - (p * plNormal)
609 #d = plNormal * (plVert0 - p)
611 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
615 Distance = staticmethod(Distance)
619 # make one or two new faces based on a list of vertex-indices
648 makeFaces = staticmethod(makeFaces)
650 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
651 """Split P using the plane of Q.
652 Logic taken from the knife.py python script
655 # Check if P and Q are parallel
656 u = CrossVecs(Vector(Q.no),Vector(P.no))
662 print "PARALLEL planes!!"
666 # The final aim is to find the intersection line between P
667 # and the plane of Q, and split P along this line
671 # Calculate point-plane Distance between vertices of P and plane Q
673 for i in range(0, nP):
674 d.append(HSR.Distance(P.v[i], Q))
687 #print "d0:", d0, "d1:", d1
689 # if the vertex lies in the cutplane
691 #print "d1 On cutplane"
692 posVertList.append(V1)
693 negVertList.append(V1)
695 # if the previous vertex lies in cutplane
697 #print "d0 on Cutplane"
699 #print "d1 on positive Halfspace"
700 posVertList.append(V1)
702 #print "d1 on negative Halfspace"
703 negVertList.append(V1)
705 # if they are on the same side of the plane
707 #print "On the same half-space"
709 #print "d1 on positive Halfspace"
710 posVertList.append(V1)
712 #print "d1 on negative Halfspace"
713 negVertList.append(V1)
715 # the vertices are not on the same side of the plane, so we have an intersection
717 #print "Intersection"
719 e = Vector(V0), Vector(V1)
720 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
722 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
727 #print "Intersection", inters
729 nv = NMesh.Vert(inters[0], inters[1], inters[2])
730 newVertList.append(nv)
732 posVertList.append(nv)
733 negVertList.append(nv)
736 posVertList.append(V1)
738 negVertList.append(V1)
741 # uniq for python > 2.4
742 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
743 #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
745 # a more portable way
746 posVertList = uniq(posVertList)
747 negVertList = uniq(negVertList)
750 # If vertex are all on the same half-space, return
751 #if len(posVertList) < 3:
752 # print "Problem, we created a face with less that 3 vertices??"
754 #if len(negVertList) < 3:
755 # print "Problem, we created a face with less that 3 vertices??"
758 if len(posVertList) < 3 or len(negVertList) < 3:
759 #print "RETURN NONE, SURE???"
762 if not return_positive_faces:
764 if not return_negative_faces:
767 newfaces = HSR.addNewFaces(posVertList, negVertList)
771 splitOn = staticmethod(splitOn)
773 def addNewFaces(posVertList, negVertList):
774 # Create new faces resulting from the split
776 if len(posVertList) or len(negVertList):
778 #newfaces = [posVertList] + [negVertList]
779 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
780 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
784 outfaces += HSR.makeFaces(nf)
789 addNewFaces = staticmethod(addNewFaces)
792 # ---------------------------------------------------------------------
794 ## Mesh Utility class
796 # ---------------------------------------------------------------------
800 def buildEdgeFaceUsersCache(me):
802 Takes a mesh and returns a list aligned with the meshes edges.
803 Each item is a list of the faces that use the edge
804 would be the equiv for having ed.face_users as a property
806 Taken from .blender/scripts/bpymodules/BPyMesh.py,
807 thanks to ideasman_42.
810 def sorted_edge_indicies(ed):
818 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
820 fvi= [v.index for v in f.v]# face vert idx's
821 for i in xrange(len(f)):
828 face_edges_dict[i1,i2][1].append(f)
830 face_edges= [None] * len(me.edges)
831 for ed_index, ed_faces in face_edges_dict.itervalues():
832 face_edges[ed_index]= ed_faces
836 def isMeshEdge(adjacent_faces):
839 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
840 Note: if the edge has no adjacent faces we want to show it as well,
841 useful for "edge only" portion of objects.
844 if len(adjacent_faces) == 0:
847 selected_faces = [f for f in adjacent_faces if f.sel]
849 if len(selected_faces) != 0:
854 def isSilhouetteEdge(adjacent_faces):
855 """Silhuette selection rule.
857 An edge is a silhuette edge if it is shared by two faces with
858 different selection status or if it is a boundary edge of a selected
862 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
863 (len(adjacent_faces) == 2 and
864 adjacent_faces[0].sel != adjacent_faces[1].sel)
870 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
871 isMeshEdge = staticmethod(isMeshEdge)
872 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
875 # ---------------------------------------------------------------------
877 ## Shading Utility class
879 # ---------------------------------------------------------------------
885 def toonShadingMapSetup():
886 levels = config.polygons['TOON_LEVELS']
888 texels = 2*levels - 1
889 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
895 shademap = ShadingUtils.shademap
898 shademap = ShadingUtils.toonShadingMapSetup()
901 for i in xrange(0, len(shademap)-1):
902 pivot = (shademap[i]+shademap[i+1])/2.0
907 if v < shademap[i+1]:
912 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
913 toonShading = staticmethod(toonShading)
916 # ---------------------------------------------------------------------
918 ## Projections classes
920 # ---------------------------------------------------------------------
923 """Calculate the projection of an object given the camera.
925 A projector is useful to so some per-object transformation to obtain the
926 projection of an object given the camera.
928 The main method is #doProjection# see the method description for the
932 def __init__(self, cameraObj, canvasRatio):
933 """Calculate the projection matrix.
935 The projection matrix depends, in this case, on the camera settings.
936 TAKE CARE: This projector expects vertices in World Coordinates!
939 camera = cameraObj.getData()
941 aspect = float(canvasRatio[0])/float(canvasRatio[1])
942 near = camera.clipStart
945 scale = float(camera.scale)
947 fovy = atan(0.5/aspect/(camera.lens/32))
948 fovy = fovy * 360.0/pi
951 if Blender.Get('version') < 243:
958 # What projection do we want?
959 if camera.type == camPersp:
960 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
961 elif camera.type == camOrtho:
962 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
965 # View transformation
966 cam = Matrix(cameraObj.getInverseMatrix())
971 self.projectionMatrix = mP
977 def doProjection(self, v):
978 """Project the point on the view plane.
980 Given a vertex calculate the projection using the current projection
984 # Note that we have to work on the vertex using homogeneous coordinates
985 # From blender 2.42+ we don't need to resize the vector to be 4d
986 # when applying a 4x4 matrix, but we do that anyway since we need the
987 # 4th coordinate later
988 p = self.projectionMatrix * Vector(v).resize4D()
990 # Perspective division
1007 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
1008 """Return a perspective projection matrix.
1011 top = near * tan(fovy * pi / 360.0)
1013 left = bottom*aspect
1015 x = (2.0 * near) / (right-left)
1016 y = (2.0 * near) / (top-bottom)
1017 a = (right+left) / (right-left)
1018 b = (top+bottom) / (top - bottom)
1019 c = - ((far+near) / (far-near))
1020 d = - ((2*far*near)/(far-near))
1026 [0.0, 0.0, -1.0, 0.0])
1030 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
1031 """Return an orthogonal projection matrix.
1034 # The 11 in the formula was found emiprically
1035 top = near * tan(fovy * pi / 360.0) * (scale * 11)
1037 left = bottom * aspect
1042 tx = -((right+left)/rl)
1043 ty = -((top+bottom)/tb)
1044 tz = ((far+near)/fn)
1047 [2.0/rl, 0.0, 0.0, tx],
1048 [0.0, 2.0/tb, 0.0, ty],
1049 [0.0, 0.0, 2.0/fn, tz],
1050 [0.0, 0.0, 0.0, 1.0])
1055 # ---------------------------------------------------------------------
1057 ## Progress Indicator
1059 # ---------------------------------------------------------------------
1062 """A model for a progress indicator.
1064 Do the progress calculation calculation and
1065 the view independent stuff of a progress indicator.
1067 def __init__(self, steps=0):
1073 def setSteps(self, steps):
1074 """Set the number of steps of the activity wich we want to track.
1081 def setName(self, name):
1082 """Set the name of the activity wich we want to track.
1089 def getProgress(self):
1090 return self.progress
1097 """Update the model, call this method when one step is completed.
1099 if self.progress == 100:
1103 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1104 self.progress = int(self.progress)
1109 class ProgressIndicator:
1110 """An abstraction of a View for the Progress Model
1114 # Use a refresh rate so we do not show the progress at
1115 # every update, but every 'self.refresh_rate' times.
1116 self.refresh_rate = 10
1117 self.shows_counter = 0
1121 self.progressModel = None
1123 def setQuiet(self, value):
1126 def setActivity(self, name, steps):
1127 """Initialize the Model.
1129 In a future version (with subactivities-progress support) this method
1130 could only set the current activity.
1132 self.progressModel = Progress()
1133 self.progressModel.setName(name)
1134 self.progressModel.setSteps(steps)
1136 def getActivity(self):
1137 return self.progressModel
1140 """Update the model and show the actual progress.
1142 assert(self.progressModel)
1144 if self.progressModel.update():
1148 self.show(self.progressModel.getProgress(),
1149 self.progressModel.getName())
1151 # We return always True here so we can call the update() method also
1152 # from lambda funcs (putting the call in logical AND with other ops)
1155 def show(self, progress, name=""):
1156 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1157 if self.shows_counter != 0:
1161 self.shows_counter = -1
1164 class ConsoleProgressIndicator(ProgressIndicator):
1165 """Show a progress bar on stderr, a la wget.
1168 ProgressIndicator.__init__(self)
1170 self.swirl_chars = ["-", "\\", "|", "/"]
1171 self.swirl_count = -1
1173 def show(self, progress, name):
1174 ProgressIndicator.show(self, progress, name)
1177 bar_progress = int( (progress/100.0) * bar_length )
1178 bar = ("=" * bar_progress).ljust(bar_length)
1180 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1181 swirl_char = self.swirl_chars[self.swirl_count]
1183 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1185 sys.stderr.write(progress_bar+"\r")
1187 sys.stderr.write("\n")
1190 class GraphicalProgressIndicator(ProgressIndicator):
1191 """Interface to the Blender.Window.DrawProgressBar() method.
1194 ProgressIndicator.__init__(self)
1196 #self.swirl_chars = ["-", "\\", "|", "/"]
1197 # We have to use letters with the same width, for now!
1198 # Blender progress bar considers the font widths when
1199 # calculating the progress bar width.
1200 self.swirl_chars = ["\\", "/"]
1201 self.swirl_count = -1
1203 def show(self, progress, name):
1204 ProgressIndicator.show(self, progress)
1206 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1207 swirl_char = self.swirl_chars[self.swirl_count]
1209 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1211 # Finally draw the Progress Bar
1212 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1213 Window.DrawProgressBar(progress/100.0, progress_text)
1216 Window.DrawProgressBar(1, progress_text)
1217 Window.WaitCursor(0)
1221 # ---------------------------------------------------------------------
1223 ## 2D Object representation class
1225 # ---------------------------------------------------------------------
1227 # TODO: a class to represent the needed properties of a 2D vector image
1228 # For now just using a [N]Mesh structure.
1231 # ---------------------------------------------------------------------
1233 ## Vector Drawing Classes
1235 # ---------------------------------------------------------------------
1241 A class for printing output in a vectorial format.
1243 Given a 2D representation of the 3D scene the class is responsible to
1244 write it is a vector format.
1246 Every subclasses of VectorWriter must have at last the following public
1250 - printCanvas(self, scene,
1251 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1254 def __init__(self, fileName):
1255 """Set the output file name and other properties"""
1260 config.writer = dict()
1261 config.writer['SETTING'] = True
1263 self.outputFileName = fileName
1265 context = Scene.GetCurrent().getRenderingContext()
1266 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1268 self.fps = context.fps
1272 self.animation = False
1279 def open(self, startFrame=1, endFrame=1):
1280 if startFrame != endFrame:
1281 self.startFrame = startFrame
1282 self.endFrame = endFrame
1283 self.animation = True
1285 print "Outputting to: ", self.outputFileName
1292 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1293 showHiddenEdges=False):
1294 """This is the interface for the needed printing routine.
1301 class SVGVectorWriter(VectorWriter):
1302 """A concrete class for writing SVG output.
1305 def __init__(self, fileName):
1306 """Simply call the parent Contructor.
1308 VectorWriter.__init__(self, fileName)
1317 def open(self, startFrame=1, endFrame=1):
1318 """Do some initialization operations.
1320 VectorWriter.open(self, startFrame, endFrame)
1322 self.file = open(self.outputFileName, "w")
1327 """Do some finalization operation.
1334 # remember to call the close method of the parent as last
1335 VectorWriter.close(self)
1338 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1339 showHiddenEdges=False):
1340 """Convert the scene representation to SVG.
1343 Objects = scene.objects
1345 context = scene.getRenderingContext()
1346 framenumber = context.currentFrame()
1349 framestyle = "display:none"
1351 framestyle = "display:block"
1353 # Assign an id to this group so we can set properties on it using DOM
1354 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1355 (framenumber, framestyle) )
1360 if(obj.getType() != 'Mesh'):
1363 self.file.write("<g id=\"%s\">\n" % obj.getName())
1365 mesh = obj.getData(mesh=1)
1368 self._printPolygons(mesh)
1371 self._printEdges(mesh, showHiddenEdges)
1373 self.file.write("</g>\n")
1375 self.file.write("</g>\n")
1382 def _calcCanvasCoord(self, v):
1383 """Convert vertex in scene coordinates to canvas coordinates.
1386 pt = Vector([0, 0, 0])
1388 mW = float(self.canvasSize[0])/2.0
1389 mH = float(self.canvasSize[1])/2.0
1391 # rescale to canvas size
1392 pt[0] = v.co[0]*mW + mW
1393 pt[1] = v.co[1]*mH + mH
1396 # For now we want (0,0) in the top-left corner of the canvas.
1397 # Mirror and translate along y
1399 pt[1] += self.canvasSize[1]
1403 def _printHeader(self):
1404 """Print SVG header."""
1406 self.file.write("<?xml version=\"1.0\"?>\n")
1407 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1408 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1409 self.file.write("<svg version=\"1.0\"\n")
1410 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1411 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1415 delay = 1000/self.fps
1417 self.file.write("""\n<script type="text/javascript"><![CDATA[
1418 globalStartFrame=%d;
1421 timerID = setInterval("NextFrame()", %d);
1422 globalFrameCounter=%d;
1423 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1425 self.file.write("""\n
1426 function NextFrame()
1428 currentElement = document.getElementById('frame'+globalFrameCounter)
1429 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1431 if (!currentElement)
1436 if (globalFrameCounter > globalEndFrame)
1438 clearInterval(timerID)
1444 previousElement.style.display="none";
1446 currentElement.style.display="block";
1447 globalFrameCounter++;
1453 def _printFooter(self):
1454 """Print the SVG footer."""
1456 self.file.write("\n</svg>\n")
1458 def _printPolygons(self, mesh):
1459 """Print the selected (visible) polygons.
1462 if len(mesh.faces) == 0:
1465 self.file.write("<g>\n")
1467 for face in mesh.faces:
1471 self.file.write("<path d=\"")
1473 #p = self._calcCanvasCoord(face.verts[0])
1474 p = self._calcCanvasCoord(face.v[0])
1475 self.file.write("M %g,%g L " % (p[0], p[1]))
1477 for v in face.v[1:]:
1478 p = self._calcCanvasCoord(v)
1479 self.file.write("%g,%g " % (p[0], p[1]))
1481 # get rid of the last blank space, just cosmetics here.
1482 self.file.seek(-1, 1)
1483 self.file.write(" z\"\n")
1485 # take as face color the first vertex color
1488 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1490 color = [255, 255, 255, 255]
1492 # Convert the color to the #RRGGBB form
1493 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1495 # Handle transparent polygons
1498 opacity = float(color[3])/255.0
1499 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1500 #opacity_string = "opacity: %g;" % (opacity)
1502 self.file.write("\tstyle=\"fill:" + str_col + ";")
1503 self.file.write(opacity_string)
1505 # use the stroke property to alleviate the "adjacent edges" problem,
1506 # we simulate polygon expansion using borders,
1507 # see http://www.antigrain.com/svg/index.html for more info
1510 # EXPANSION TRICK is not that useful where there is transparency
1511 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1512 # str_col = "#000000" # For debug
1513 self.file.write(" stroke:%s;\n" % str_col)
1514 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1515 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1517 self.file.write("\"/>\n")
1519 self.file.write("</g>\n")
1521 def _printEdges(self, mesh, showHiddenEdges=False):
1522 """Print the wireframe using mesh edges.
1525 stroke_width = config.edges['WIDTH']
1526 stroke_col = config.edges['COLOR']
1528 self.file.write("<g>\n")
1530 for e in mesh.edges:
1532 hidden_stroke_style = ""
1535 if showHiddenEdges == False:
1538 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1540 p1 = self._calcCanvasCoord(e.v1)
1541 p2 = self._calcCanvasCoord(e.v2)
1543 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1544 % ( p1[0], p1[1], p2[0], p2[1] ) )
1545 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1546 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1547 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1548 self.file.write(hidden_stroke_style)
1549 self.file.write("\"/>\n")
1551 self.file.write("</g>\n")
1560 SWFSupported = False
1562 class SWFVectorWriter(VectorWriter):
1563 """A concrete class for writing SWF output.
1566 def __init__(self, fileName):
1567 """Simply call the parent Contructor.
1569 VectorWriter.__init__(self, fileName)
1579 def open(self, startFrame=1, endFrame=1):
1580 """Do some initialization operations.
1582 VectorWriter.open(self, startFrame, endFrame)
1583 self.movie = SWFMovie()
1584 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1586 self.movie.setRate(self.fps)
1587 numframes = endFrame - startFrame + 1
1588 self.movie.setFrames(numframes)
1591 """Do some finalization operation.
1593 self.movie.save(self.outputFileName)
1595 # remember to call the close method of the parent
1596 VectorWriter.close(self)
1598 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1599 showHiddenEdges=False):
1600 """Convert the scene representation to SVG.
1602 context = scene.getRenderingContext()
1603 framenumber = context.currentFrame()
1605 Objects = scene.objects
1608 self.movie.remove(self.sprite)
1610 sprite = SWFSprite()
1614 if(obj.getType() != 'Mesh'):
1617 mesh = obj.getData(mesh=1)
1620 self._printPolygons(mesh, sprite)
1623 self._printEdges(mesh, sprite, showHiddenEdges)
1626 i = self.movie.add(sprite)
1627 # Remove the instance the next time
1630 self.movie.nextFrame()
1637 def _calcCanvasCoord(self, v):
1638 """Convert vertex in scene coordinates to canvas coordinates.
1641 pt = Vector([0, 0, 0])
1643 mW = float(self.canvasSize[0])/2.0
1644 mH = float(self.canvasSize[1])/2.0
1646 # rescale to canvas size
1647 pt[0] = v.co[0]*mW + mW
1648 pt[1] = v.co[1]*mH + mH
1651 # For now we want (0,0) in the top-left corner of the canvas.
1652 # Mirror and translate along y
1654 pt[1] += self.canvasSize[1]
1658 def _printPolygons(self, mesh, sprite):
1659 """Print the selected (visible) polygons.
1662 if len(mesh.faces) == 0:
1665 for face in mesh.faces:
1671 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1673 color = [255, 255, 255, 255]
1676 f = s.addFill(color[0], color[1], color[2], color[3])
1679 # The starting point of the shape
1680 p0 = self._calcCanvasCoord(face.verts[0])
1681 s.movePenTo(p0[0], p0[1])
1683 for v in face.verts[1:]:
1684 p = self._calcCanvasCoord(v)
1685 s.drawLineTo(p[0], p[1])
1688 s.drawLineTo(p0[0], p0[1])
1694 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1695 """Print the wireframe using mesh edges.
1698 stroke_width = config.edges['WIDTH']
1699 stroke_col = config.edges['COLOR']
1703 for e in mesh.edges:
1705 # Next, we set the line width and color for our shape.
1706 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1710 if showHiddenEdges == False:
1713 # SWF does not support dashed lines natively, so -for now-
1714 # draw hidden lines thinner and half-trasparent
1715 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1718 p1 = self._calcCanvasCoord(e.v1)
1719 p2 = self._calcCanvasCoord(e.v2)
1721 s.movePenTo(p1[0], p1[1])
1722 s.drawLineTo(p2[0], p2[1])
1731 from reportlab.pdfgen import canvas
1734 PDFSupported = False
1736 class PDFVectorWriter(VectorWriter):
1737 """A concrete class for writing PDF output.
1740 def __init__(self, fileName):
1741 """Simply call the parent Contructor.
1743 VectorWriter.__init__(self, fileName)
1752 def open(self, startFrame=1, endFrame=1):
1753 """Do some initialization operations.
1755 VectorWriter.open(self, startFrame, endFrame)
1756 size = (self.canvasSize[0], self.canvasSize[1])
1757 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1760 """Do some finalization operation.
1764 # remember to call the close method of the parent
1765 VectorWriter.close(self)
1767 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1768 showHiddenEdges=False):
1769 """Convert the scene representation to SVG.
1771 context = scene.getRenderingContext()
1772 framenumber = context.currentFrame()
1774 Objects = scene.objects
1778 if(obj.getType() != 'Mesh'):
1781 mesh = obj.getData(mesh=1)
1784 self._printPolygons(mesh)
1787 self._printEdges(mesh, showHiddenEdges)
1789 self.canvas.showPage()
1795 def _calcCanvasCoord(self, v):
1796 """Convert vertex in scene coordinates to canvas coordinates.
1799 pt = Vector([0, 0, 0])
1801 mW = float(self.canvasSize[0])/2.0
1802 mH = float(self.canvasSize[1])/2.0
1804 # rescale to canvas size
1805 pt[0] = v.co[0]*mW + mW
1806 pt[1] = v.co[1]*mH + mH
1809 # For now we want (0,0) in the top-left corner of the canvas.
1810 # Mirror and translate along y
1812 pt[1] += self.canvasSize[1]
1816 def _printPolygons(self, mesh):
1817 """Print the selected (visible) polygons.
1820 if len(mesh.faces) == 0:
1823 for face in mesh.faces:
1829 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1832 color = [1, 1, 1, 1]
1834 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1836 self.canvas.setStrokeColorRGB(0, 0, 0)
1838 path = self.canvas.beginPath()
1840 # The starting point of the path
1841 p0 = self._calcCanvasCoord(face.verts[0])
1842 path.moveTo(p0[0], p0[1])
1844 for v in face.verts[1:]:
1845 p = self._calcCanvasCoord(v)
1846 path.lineTo(p[0], p[1])
1851 self.canvas.drawPath(path, stroke=0, fill=1)
1853 def _printEdges(self, mesh, showHiddenEdges=False):
1854 """Print the wireframe using mesh edges.
1857 stroke_width = config.edges['WIDTH']
1858 stroke_col = config.edges['COLOR']
1860 self.canvas.setLineCap(1)
1861 self.canvas.setLineJoin(1)
1862 self.canvas.setLineWidth(stroke_width)
1863 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1866 for e in mesh.edges:
1868 self.canvas.setLineWidth(stroke_width)
1871 if showHiddenEdges == False:
1874 # PDF does not support dashed lines natively, so -for now-
1875 # draw hidden lines thinner
1876 self.canvas.setLineWidth(stroke_width/2.0)
1878 p1 = self._calcCanvasCoord(e.v1)
1879 p2 = self._calcCanvasCoord(e.v2)
1881 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1885 # ---------------------------------------------------------------------
1887 ## Rendering Classes
1889 # ---------------------------------------------------------------------
1891 # A dictionary to collect different shading style methods
1892 shadingStyles = dict()
1893 shadingStyles['FLAT'] = None
1894 shadingStyles['TOON'] = None
1896 # A dictionary to collect different edge style methods
1898 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1899 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1901 # A dictionary to collect the supported output formats
1902 outputWriters = dict()
1903 outputWriters['SVG'] = SVGVectorWriter
1905 outputWriters['SWF'] = SWFVectorWriter
1907 outputWriters['PDF'] = PDFVectorWriter
1911 """Render a scene viewed from the active camera.
1913 This class is responsible of the rendering process, transformation and
1914 projection of the objects in the scene are invoked by the renderer.
1916 The rendering is done using the active camera for the current scene.
1920 """Make the rendering process only for the current scene by default.
1922 We will work on a copy of the scene, to be sure that the current scene do
1923 not get modified in any way.
1926 # Render the current Scene, this should be a READ-ONLY property
1927 self._SCENE = Scene.GetCurrent()
1929 # Use the aspect ratio of the scene rendering context
1930 context = self._SCENE.getRenderingContext()
1932 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1933 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1934 float(context.aspectRatioY())
1937 # Render from the currently active camera
1938 #self.cameraObj = self._SCENE.objects.camera
1947 def doRendering(self, outputWriter, animation=False):
1948 """Render picture or animation and write it out.
1951 - a Vector writer object that will be used to output the result.
1952 - a flag to tell if we want to render an animation or only the
1956 context = self._SCENE.getRenderingContext()
1957 origCurrentFrame = context.currentFrame()
1959 # Handle the animation case
1961 startFrame = origCurrentFrame
1962 endFrame = startFrame
1965 startFrame = context.startFrame()
1966 endFrame = context.endFrame()
1967 outputWriter.open(startFrame, endFrame)
1969 # Do the rendering process frame by frame
1970 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1971 for f in xrange(startFrame, endFrame+1):
1972 print "\n\nFrame: %d" % f
1974 # FIXME To get the correct camera position we have to use +1 here.
1975 # Is there a bug somewhere in the Scene module?
1976 context.currentFrame(f+1)
1977 self.cameraObj = self._SCENE.objects.camera
1979 # Use some temporary workspace, a full copy of the scene
1980 inputScene = self._SCENE.copy(2)
1982 # To get the objects at this frame remove the +1 ...
1983 ctx = inputScene.getRenderingContext()
1987 # Get a projector for this camera.
1988 # NOTE: the projector wants object in world coordinates,
1989 # so we should remember to apply modelview transformations
1990 # _before_ we do projection transformations.
1991 self.proj = Projector(self.cameraObj, self.canvasRatio)
1994 renderedScene = self.doRenderScene(inputScene)
1996 print "There was an error! Aborting."
1998 print traceback.print_exc()
2000 self._SCENE.makeCurrent()
2001 Scene.Unlink(inputScene)
2005 outputWriter.printCanvas(renderedScene,
2006 doPrintPolygons = config.polygons['SHOW'],
2007 doPrintEdges = config.edges['SHOW'],
2008 showHiddenEdges = config.edges['SHOW_HIDDEN'])
2010 # delete the rendered scene
2011 self._SCENE.makeCurrent()
2012 Scene.Unlink(renderedScene)
2015 outputWriter.close()
2017 context.currentFrame(origCurrentFrame)
2020 def doRenderScene(self, workScene):
2021 """Control the rendering process.
2023 Here we control the entire rendering process invoking the operation
2024 needed to transform and project the 3D scene in two dimensions.
2027 # global processing of the scene
2029 self._filterHiddenObjects(workScene)
2031 self._buildLightSetup(workScene)
2033 self._doSceneClipping(workScene)
2035 self._doConvertGeometricObjsToMesh(workScene)
2037 if config.output['JOIN_OBJECTS']:
2038 self._joinMeshObjectsInScene(workScene)
2040 self._doSceneDepthSorting(workScene)
2042 # Per object activities
2044 Objects = workScene.objects
2046 print "Total Objects: %d" % len(Objects)
2047 for i,obj in enumerate(Objects):
2049 print "Rendering Object: %d" % i
2051 if obj.getType() != 'Mesh':
2052 print "Only Mesh supported! - Skipping type:", obj.getType()
2055 print "Rendering: ", obj.getName()
2057 mesh = obj.getData(mesh=1)
2059 self._doModelingTransformation(mesh, obj.matrix)
2061 self._doBackFaceCulling(mesh)
2064 # When doing HSR with NEWELL we may want to flip all normals
2066 if config.polygons['HSR'] == "NEWELL":
2067 for f in mesh.faces:
2070 for f in mesh.faces:
2073 self._doLighting(mesh)
2075 # Do "projection" now so we perform further processing
2076 # in Normalized View Coordinates
2077 self._doProjection(mesh, self.proj)
2079 self._doViewFrustumClipping(mesh)
2081 self._doHiddenSurfaceRemoval(mesh)
2083 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2085 # Update the object data, important! :)
2097 def _getObjPosition(self, obj):
2098 """Return the obj position in World coordinates.
2100 return obj.matrix.translationPart()
2102 def _cameraViewVector(self):
2103 """Get the View Direction form the camera matrix.
2105 return Vector(self.cameraObj.matrix[2]).resize3D()
2110 def _isFaceVisible(self, face):
2111 """Determine if a face of an object is visible from the current camera.
2113 The view vector is calculated from the camera location and one of the
2114 vertices of the face (expressed in World coordinates, after applying
2115 modelview transformations).
2117 After those transformations we determine if a face is visible by
2118 computing the angle between the face normal and the view vector, this
2119 angle has to be between -90 and 90 degrees for the face to be visible.
2120 This corresponds somehow to the dot product between the two, if it
2121 results > 0 then the face is visible.
2123 There is no need to normalize those vectors since we are only interested in
2124 the sign of the cross product and not in the product value.
2126 NOTE: here we assume the face vertices are in WorldCoordinates, so
2127 please transform the object _before_ doing the test.
2130 normal = Vector(face.no)
2131 camPos = self._getObjPosition(self.cameraObj)
2134 # View Vector in orthographics projections is the view Direction of
2136 if self.cameraObj.data.getType() == 1:
2137 view_vect = self._cameraViewVector()
2139 # View vector in perspective projections can be considered as
2140 # the difference between the camera position and one point of
2141 # the face, we choose the farthest point from the camera.
2142 if self.cameraObj.data.getType() == 0:
2143 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2147 # if d > 0 the face is visible from the camera
2148 d = view_vect * normal
2158 def _filterHiddenObjects(self, scene):
2159 """Discard object that are on hidden layers in the scene.
2162 Objects = scene.objects
2164 visible_obj_list = [ obj for obj in Objects if
2165 set(obj.layers).intersection(set(scene.getLayers())) ]
2168 if o not in visible_obj_list:
2169 scene.objects.unlink(o)
2175 def _buildLightSetup(self, scene):
2176 # Get the list of lighting sources
2177 obj_lst = scene.objects
2178 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp' ]
2180 # When there are no lights we use a default lighting source
2181 # that have the same position of the camera
2182 if len(self.lights) == 0:
2183 l = Lamp.New('Lamp')
2184 lobj = Object.New('Lamp')
2185 lobj.loc = self.cameraObj.loc
2187 self.lights.append(lobj)
2190 def _doSceneClipping(self, scene):
2191 """Clip whole objects against the View Frustum.
2193 For now clip away only objects according to their center position.
2196 cam_pos = self._getObjPosition(self.cameraObj)
2197 view_vect = self._cameraViewVector()
2199 near = self.cameraObj.data.clipStart
2200 far = self.cameraObj.data.clipEnd
2202 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2203 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2204 fovy = fovy * 360.0/pi
2206 Objects = scene.objects
2209 if o.getType() != 'Mesh': continue;
2212 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2214 d = obj_vect*view_vect
2215 theta = AngleBetweenVecs(obj_vect, view_vect)
2217 # if the object is outside the view frustum, clip it away
2218 if (d < near) or (d > far) or (theta > fovy):
2219 scene.objects.unlink(o)
2222 # Use the object bounding box
2223 # (whose points are already in WorldSpace Coordinate)
2225 bb = o.getBoundBox()
2229 p_vect = Vector(cam_pos) - Vector(p)
2231 d = p_vect * view_vect
2232 theta = AngleBetweenVecs(p_vect, view_vect)
2234 # Is this point outside the view frustum?
2235 if (d < near) or (d > far) or (theta > fovy):
2238 # If the bb is all outside the view frustum we clip the whole
2240 if points_outside == len(bb):
2241 scene.objects.unlink(o)
2245 def _doConvertGeometricObjsToMesh(self, scene):
2246 """Convert all "geometric" objects to mesh ones.
2248 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2249 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2251 Objects = scene.objects
2253 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2256 obj = self._convertToRawMeshObj(obj)
2257 scene.objects.link(obj)
2258 scene.objects.unlink(old_obj)
2261 # XXX Workaround for Text and Curve which have some normals
2262 # inverted when they are converted to Mesh, REMOVE that when
2263 # blender will fix that!!
2264 if old_obj.getType() in ['Curve', 'Text']:
2265 me = obj.getData(mesh=1)
2266 for f in me.faces: f.sel = 1;
2267 for v in me.verts: v.sel = 1;
2274 def _doSceneDepthSorting(self, scene):
2275 """Sort objects in the scene.
2277 The object sorting is done accordingly to the object centers.
2280 c = self._getObjPosition(self.cameraObj)
2282 by_obj_center_pos = (lambda o1, o2:
2283 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2284 cmp((self._getObjPosition(o1) - Vector(c)).length,
2285 (self._getObjPosition(o2) - Vector(c)).length)
2288 # Implement sorting by bounding box, the object with the bb
2289 # nearest to the camera should be drawn as last.
2290 by_nearest_bbox_point = (lambda o1, o2:
2291 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2292 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2293 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2298 Objects = list(scene.objects)
2300 #Objects.sort(by_obj_center_pos)
2301 Objects.sort(by_nearest_bbox_point)
2305 scene.objects.unlink(o)
2306 scene.objects.link(o)
2308 def _joinMeshObjectsInScene(self, scene):
2309 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2312 oList = [o for o in scene.objects if o.getType()=='Mesh']
2314 # FIXME: Object.join() do not work if the list contains 1 object
2318 mesh = Mesh.New('BigOne')
2319 bigObj = Object.New('Mesh', 'BigOne')
2322 scene.objects.link(bigObj)
2326 except RuntimeError:
2327 print "\nWarning! - Can't Join Objects\n"
2328 scene.objects.unlink(bigObj)
2331 print "Objects Type error?"
2334 scene.objects.unlink(o)
2339 # Per object/mesh methods
2341 def _convertToRawMeshObj(self, object):
2342 """Convert geometry based object to a mesh object.
2344 me = Mesh.New('RawMesh_'+object.name)
2345 me.getFromObject(object.name)
2347 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2350 # If the object has no materials set a default material
2351 if not me.materials:
2352 me.materials = [Material.New()]
2353 #for f in me.faces: f.mat = 0
2355 newObject.setMatrix(object.getMatrix())
2359 def _doModelingTransformation(self, mesh, matrix):
2360 """Transform object coordinates to world coordinates.
2362 This step is done simply applying to the object its tranformation
2363 matrix and recalculating its normals.
2365 # XXX FIXME: blender do not transform normals in the right way when
2366 # there are negative scale values
2367 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2368 print "WARNING: Negative scales, expect incorrect results!"
2370 mesh.transform(matrix, True)
2372 def _doBackFaceCulling(self, mesh):
2373 """Simple Backface Culling routine.
2375 At this level we simply do a visibility test face by face and then
2376 select the vertices belonging to visible faces.
2379 # Select all vertices, so edges can be displayed even if there are no
2381 for v in mesh.verts:
2384 Mesh.Mode(Mesh.SelectModes['FACE'])
2386 for f in mesh.faces:
2388 if self._isFaceVisible(f):
2391 def _doLighting(self, mesh):
2392 """Apply an Illumination and shading model to the object.
2394 The model used is the Phong one, it may be inefficient,
2395 but I'm just learning about rendering and starting from Phong seemed
2396 the most natural way.
2399 # If the mesh has vertex colors already, use them,
2400 # otherwise turn them on and do some calculations
2401 if mesh.vertexColors:
2403 mesh.vertexColors = 1
2405 materials = mesh.materials
2407 camPos = self._getObjPosition(self.cameraObj)
2409 # We do per-face color calculation (FLAT Shading), we can easily turn
2410 # to a per-vertex calculation if we want to implement some shading
2411 # technique. For an example see:
2412 # http://www.miralab.unige.ch/papers/368.pdf
2413 for f in mesh.faces:
2419 mat = materials[f.mat]
2421 # A new default material
2423 mat = Material.New('defMat')
2425 # Check if it is a shadeless material
2426 elif mat.getMode() & Material.Modes['SHADELESS']:
2428 # Convert to a value between 0 and 255
2429 tmp_col = [ int(c * 255.0) for c in I]
2440 # do vertex color calculation
2442 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2444 for l in self.lights:
2446 light_pos = self._getObjPosition(l)
2447 light = light_obj.getData()
2449 L = Vector(light_pos).normalize()
2451 V = (Vector(camPos) - Vector(f.cent)).normalize()
2453 N = Vector(f.no).normalize()
2455 if config.polygons['SHADING'] == 'TOON':
2456 NL = ShadingUtils.toonShading(N*L)
2460 # Should we use NL instead of (N*L) here?
2461 R = 2 * (N*L) * N - L
2463 Ip = light.getEnergy()
2465 # Diffuse co-efficient
2466 kd = mat.getRef() * Vector(mat.getRGBCol())
2468 kd[i] *= light.col[i]
2470 Idiff = Ip * kd * max(0, NL)
2473 # Specular component
2474 ks = mat.getSpec() * Vector(mat.getSpecCol())
2475 ns = mat.getHardness()
2476 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2478 TotDiffSpec += (Idiff+Ispec)
2482 Iamb = Vector(Blender.World.Get()[0].getAmb())
2485 # Emissive component (convert to a triplet)
2486 ki = Vector([mat.getEmit()]*3)
2488 #I = ki + Iamb + (Idiff + Ispec)
2489 I = ki + (ka * Iamb) + TotDiffSpec
2492 # Set Alpha component
2494 I.append(mat.getAlpha())
2496 # Clamp I values between 0 and 1
2497 I = [ min(c, 1) for c in I]
2498 I = [ max(0, c) for c in I]
2500 # Convert to a value between 0 and 255
2501 tmp_col = [ int(c * 255.0) for c in I]
2509 def _doProjection(self, mesh, projector):
2510 """Apply Viewing and Projection tranformations.
2513 for v in mesh.verts:
2514 p = projector.doProjection(v.co[:])
2519 #mesh.recalcNormals()
2522 # We could reeset Camera matrix, since now
2523 # we are in Normalized Viewing Coordinates,
2524 # but doung that would affect World Coordinate
2525 # processing for other objects
2527 #self.cameraObj.data.type = 1
2528 #self.cameraObj.data.scale = 2.0
2529 #m = Matrix().identity()
2530 #self.cameraObj.setMatrix(m)
2532 def _doViewFrustumClipping(self, mesh):
2533 """Clip faces against the View Frustum.
2536 # The Canonical View Volume, 8 vertices, and 6 faces,
2537 # We consider its face normals pointing outside
2539 v1 = NMesh.Vert(1, 1, -1)
2540 v2 = NMesh.Vert(1, -1, -1)
2541 v3 = NMesh.Vert(-1, -1, -1)
2542 v4 = NMesh.Vert(-1, 1, -1)
2543 v5 = NMesh.Vert(1, 1, 1)
2544 v6 = NMesh.Vert(1, -1, 1)
2545 v7 = NMesh.Vert(-1, -1, 1)
2546 v8 = NMesh.Vert(-1, 1, 1)
2549 f1 = NMesh.Face([v1, v4, v3, v2])
2551 f2 = NMesh.Face([v5, v6, v7, v8])
2553 f3 = NMesh.Face([v1, v2, v6, v5])
2555 f4 = NMesh.Face([v2, v3, v7, v6])
2557 f5 = NMesh.Face([v3, v4, v8, v7])
2559 f6 = NMesh.Face([v4, v1, v5, v8])
2562 nmesh = NMesh.GetRaw(mesh.name)
2563 clippedfaces = nmesh.faces[:]
2564 facelist = clippedfaces[:]
2566 for clipface in cvv:
2572 #newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2576 # Check if the face is all outside the view frustum
2577 # TODO: Do this test before, it is more efficient
2580 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2583 if points_outside != len(f):
2584 clippedfaces.append(f)
2588 nmesh.verts.append(v)
2592 nf.col = [f.col[0]] * len(nf.v)
2594 clippedfaces.append(nf)
2595 facelist = clippedfaces[:]
2598 nmesh.faces = facelist
2603 def __simpleDepthSort(self, mesh):
2604 """Sort faces by the furthest vertex.
2606 This simple mesthod is known also as the painter algorithm, and it
2607 solves HSR correctly only for convex meshes.
2612 # The sorting requires circa n*log(n) steps
2614 progress.setActivity("HSR: Painter", n*log(n))
2616 by_furthest_z = (lambda f1, f2: progress.update() and
2617 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2620 # FIXME: using NMesh to sort faces. We should avoid that!
2621 nmesh = NMesh.GetRaw(mesh.name)
2623 # remember that _higher_ z values mean further points
2624 nmesh.faces.sort(by_furthest_z)
2625 nmesh.faces.reverse()
2630 def __newellDepthSort(self, mesh):
2631 """Newell's depth sorting.
2637 # Find non planar quads and convert them to triangle
2638 #for f in mesh.faces:
2640 # if is_nonplanar_quad(f.v):
2641 # print "NON QUAD??"
2645 # Now reselect all faces
2646 for f in mesh.faces:
2648 mesh.quadToTriangle()
2650 # FIXME: using NMesh to sort faces. We should avoid that!
2651 nmesh = NMesh.GetRaw(mesh.name)
2653 # remember that _higher_ z values mean further points
2654 nmesh.faces.sort(by_furthest_z)
2655 nmesh.faces.reverse()
2657 # Begin depth sort tests
2659 # use the smooth flag to set marked faces
2660 for f in nmesh.faces:
2663 facelist = nmesh.faces[:]
2667 # The steps are _at_least_ equal to len(facelist), we do not count the
2668 # feces coming out from splitting!!
2669 progress.setActivity("HSR: Newell", len(facelist))
2670 #progress.setQuiet(True)
2673 while len(facelist):
2674 debug("\n----------------------\n")
2675 debug("len(facelits): %d\n" % len(facelist))
2678 pSign = sign(P.normal[2])
2680 # We can discard faces parallel to the view vector
2681 #if P.normal[2] == 0:
2682 # facelist.remove(P)
2688 for Q in facelist[1:]:
2690 debug("P.smooth: " + str(P.smooth) + "\n")
2691 debug("Q.smooth: " + str(Q.smooth) + "\n")
2694 qSign = sign(Q.normal[2])
2695 # TODO: check also if Q is parallel??
2697 # Test 0: We need to test only those Qs whose furthest vertex
2698 # is closer to the observer than the closest vertex of P.
2700 zP = [v.co[2] for v in P.v]
2701 zQ = [v.co[2] for v in Q.v]
2702 notZOverlap = min(zP) > max(zQ) + EPS
2706 debug("NOT Z OVERLAP!\n")
2708 # If Q is not marked then we can safely print P
2711 debug("met a marked face\n")
2715 # Test 1: X extent overlapping
2716 xP = [v.co[0] for v in P.v]
2717 xQ = [v.co[0] for v in Q.v]
2718 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2719 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2723 debug("NOT X OVERLAP!\n")
2727 # Test 2: Y extent Overlapping
2728 yP = [v.co[1] for v in P.v]
2729 yQ = [v.co[1] for v in Q.v]
2730 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2731 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2735 debug("NOT Y OVERLAP!\n")
2739 # Test 3: P vertices are all behind the plane of Q
2742 d = qSign * HSR.Distance(Vector(Pi), Q)
2745 pVerticesBehindPlaneQ = (n == len(P))
2747 if pVerticesBehindPlaneQ:
2749 debug("P BEHIND Q!\n")
2753 # Test 4: Q vertices in front of the plane of P
2756 d = pSign * HSR.Distance(Vector(Qi), P)
2759 qVerticesInFrontPlaneP = (n == len(Q))
2761 if qVerticesInFrontPlaneP:
2763 debug("Q IN FRONT OF P!\n")
2767 # Test 5: Check if projections of polygons effectively overlap,
2768 # in previous tests we checked only bounding boxes.
2770 #if not projectionsOverlap(P, Q):
2771 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2773 debug("Projections do not overlap!\n")
2776 # We still can't say if P obscures Q.
2778 # But if Q is marked we do a face-split trying to resolve a
2779 # difficulty (maybe a visibility cycle).
2782 debug("Possibly a cycle detected!\n")
2783 debug("Split here!!\n")
2785 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2789 # The question now is: Does Q obscure P?
2792 # Test 3bis: Q vertices are all behind the plane of P
2795 d = pSign * HSR.Distance(Vector(Qi), P)
2798 qVerticesBehindPlaneP = (n == len(Q))
2800 if qVerticesBehindPlaneP:
2801 debug("\nTest 3bis\n")
2802 debug("Q BEHIND P!\n")
2805 # Test 4bis: P vertices in front of the plane of Q
2808 d = qSign * HSR.Distance(Vector(Pi), Q)
2811 pVerticesInFrontPlaneQ = (n == len(P))
2813 if pVerticesInFrontPlaneQ:
2814 debug("\nTest 4bis\n")
2815 debug("P IN FRONT OF Q!\n")
2818 # We don't even know if Q does obscure P, so they should
2819 # intersect each other, split one of them in two parts.
2820 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2821 debug("\nSimple Intersection?\n")
2822 debug("Test 3bis or 4bis failed\n")
2823 debug("Split here!!2\n")
2825 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2830 facelist.insert(0, Q)
2833 debug("Q marked!\n")
2837 if split_done == 0 and face_marked == 0:
2840 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2844 if len(facelist) == 870:
2845 dumpfaces([P, Q], "loopdebug.svg")
2848 #if facelist == None:
2850 # print [v.co for v in P]
2851 # print [v.co for v in Q]
2854 # end of while len(facelist)
2857 nmesh.faces = maplist
2858 #for f in nmesh.faces:
2864 def _doHiddenSurfaceRemoval(self, mesh):
2865 """Do HSR for the given mesh.
2867 if len(mesh.faces) == 0:
2870 if config.polygons['HSR'] == 'PAINTER':
2871 print "\nUsing the Painter algorithm for HSR."
2872 self.__simpleDepthSort(mesh)
2874 elif config.polygons['HSR'] == 'NEWELL':
2875 print "\nUsing the Newell's algorithm for HSR."
2876 self.__newellDepthSort(mesh)
2879 def _doEdgesStyle(self, mesh, edgestyleSelect):
2880 """Process Mesh Edges accroding to a given selection style.
2882 Examples of algorithms:
2885 given an edge if its adjacent faces have the same normal (that is
2886 they are complanar), than deselect it.
2889 given an edge if one its adjacent faces is frontfacing and the
2890 other is backfacing, than select it, else deselect.
2893 Mesh.Mode(Mesh.SelectModes['EDGE'])
2895 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2897 for i,edge_faces in enumerate(edge_cache):
2898 mesh.edges[i].sel = 0
2899 if edgestyleSelect(edge_faces):
2900 mesh.edges[i].sel = 1
2903 for e in mesh.edges:
2906 if edgestyleSelect(e, mesh):
2912 # ---------------------------------------------------------------------
2914 ## GUI Class and Main Program
2916 # ---------------------------------------------------------------------
2919 from Blender import BGL, Draw
2920 from Blender.BGL import *
2926 # Output Format menu
2927 output_format = config.output['FORMAT']
2928 default_value = outputWriters.keys().index(output_format)+1
2929 GUI.outFormatMenu = Draw.Create(default_value)
2930 GUI.evtOutFormatMenu = 0
2932 # Animation toggle button
2933 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2934 GUI.evtAnimToggle = 1
2936 # Join Objects toggle button
2937 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2938 GUI.evtJoinObjsToggle = 2
2940 # Render filled polygons
2941 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2943 # Shading Style menu
2944 shading_style = config.polygons['SHADING']
2945 default_value = shadingStyles.keys().index(shading_style)+1
2946 GUI.shadingStyleMenu = Draw.Create(default_value)
2947 GUI.evtShadingStyleMenu = 21
2949 GUI.evtPolygonsToggle = 3
2950 # We hide the config.polygons['EXPANSION_TRICK'], for now
2952 # Render polygon edges
2953 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2954 GUI.evtShowEdgesToggle = 4
2956 # Render hidden edges
2957 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2958 GUI.evtShowHiddenEdgesToggle = 5
2961 edge_style = config.edges['STYLE']
2962 default_value = edgeStyles.keys().index(edge_style)+1
2963 GUI.edgeStyleMenu = Draw.Create(default_value)
2964 GUI.evtEdgeStyleMenu = 6
2967 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2968 GUI.evtEdgeWidthSlider = 7
2971 c = config.edges['COLOR']
2972 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2973 GUI.evtEdgeColorPicker = 71
2976 GUI.evtRenderButton = 8
2979 GUI.evtExitButton = 9
2981 # Save default button
2982 GUI.evtSaveDefaultButton = 99
2986 # initialize static members
2989 glClear(GL_COLOR_BUFFER_BIT)
2990 glColor3f(0.0, 0.0, 0.0)
2991 glRasterPos2i(10, 380)
2992 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2994 glRasterPos2i(10, 365)
2995 Draw.Text("%s (c) 2006, 2007" % __author__)
2997 glRasterPos2i(10, 335)
2998 Draw.Text("Press Q or ESC to quit.")
3000 # Build the output format menu
3001 glRasterPos2i(10, 310)
3002 Draw.Text("Select the output Format:")
3003 outMenuStruct = "Output Format %t"
3004 for t in outputWriters.keys():
3005 outMenuStruct = outMenuStruct + "|%s" % t
3006 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
3007 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
3010 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
3011 10, 260, 160, 18, GUI.animToggle.val,
3012 "Toggle rendering of animations")
3014 # Join Objects toggle
3015 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
3016 10, 235, 160, 18, GUI.joinObjsToggle.val,
3017 "Join objects in the rendered file")
3020 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
3022 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
3024 Draw.Button("Save settings as default", GUI.evtSaveDefaultButton, 10, 210-50, 160, 18,
3025 "Save settings as default")
3028 glRasterPos2i(200, 310)
3029 Draw.Text("Rendering Style:")
3032 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
3033 200, 285, 160, 18, GUI.polygonsToggle.val,
3034 "Render filled polygons")
3036 if GUI.polygonsToggle.val == 1:
3038 # Polygon Shading Style
3039 shadingStyleMenuStruct = "Shading Style %t"
3040 for t in shadingStyles.keys():
3041 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
3042 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
3043 200, 260, 160, 18, GUI.shadingStyleMenu.val,
3044 "Choose the shading style")
3048 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
3049 200, 235, 160, 18, GUI.showEdgesToggle.val,
3050 "Render polygon edges")
3052 if GUI.showEdgesToggle.val == 1:
3055 edgeStyleMenuStruct = "Edge Style %t"
3056 for t in edgeStyles.keys():
3057 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
3058 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
3059 200, 210, 160, 18, GUI.edgeStyleMenu.val,
3060 "Choose the edge style")
3063 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
3064 200, 185, 140, 18, GUI.edgeWidthSlider.val,
3065 0.0, 10.0, 0, "Change Edge Width")
3068 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
3069 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
3072 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
3073 GUI.evtShowHiddenEdgesToggle,
3074 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
3075 "Render hidden edges as dashed lines")
3078 def event(evt, val):
3080 if evt == Draw.ESCKEY or evt == Draw.QKEY:
3087 def button_event(evt):
3089 if evt == GUI.evtExitButton:
3092 elif evt == GUI.evtOutFormatMenu:
3093 i = GUI.outFormatMenu.val - 1
3094 config.output['FORMAT']= outputWriters.keys()[i]
3095 # Set the new output file
3097 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3099 elif evt == GUI.evtAnimToggle:
3100 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3102 elif evt == GUI.evtJoinObjsToggle:
3103 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3105 elif evt == GUI.evtPolygonsToggle:
3106 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3108 elif evt == GUI.evtShadingStyleMenu:
3109 i = GUI.shadingStyleMenu.val - 1
3110 config.polygons['SHADING'] = shadingStyles.keys()[i]
3112 elif evt == GUI.evtShowEdgesToggle:
3113 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3115 elif evt == GUI.evtShowHiddenEdgesToggle:
3116 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3118 elif evt == GUI.evtEdgeStyleMenu:
3119 i = GUI.edgeStyleMenu.val - 1
3120 config.edges['STYLE'] = edgeStyles.keys()[i]
3122 elif evt == GUI.evtEdgeWidthSlider:
3123 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3125 elif evt == GUI.evtEdgeColorPicker:
3126 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3128 elif evt == GUI.evtRenderButton:
3129 label = "Save %s" % config.output['FORMAT']
3130 # Show the File Selector
3132 Blender.Window.FileSelector(vectorize, label, outputfile)
3134 elif evt == GUI.evtSaveDefaultButton:
3135 config.saveToRegistry()
3138 print "Event: %d not handled!" % evt
3145 from pprint import pprint
3147 pprint(config.output)
3148 pprint(config.polygons)
3149 pprint(config.edges)
3151 _init = staticmethod(_init)
3152 draw = staticmethod(draw)
3153 event = staticmethod(event)
3154 button_event = staticmethod(button_event)
3155 conf_debug = staticmethod(conf_debug)
3157 # A wrapper function for the vectorizing process
3158 def vectorize(filename):
3159 """The vectorizing process is as follows:
3161 - Instanciate the writer and the renderer
3166 print "\nERROR: invalid file name!"
3169 from Blender import Window
3170 editmode = Window.EditMode()
3171 if editmode: Window.EditMode(0)
3173 actualWriter = outputWriters[config.output['FORMAT']]
3174 writer = actualWriter(filename)
3176 renderer = Renderer()
3177 renderer.doRendering(writer, config.output['ANIMATION'])
3179 if editmode: Window.EditMode(1)
3184 if __name__ == "__main__":
3188 config.loadFromRegistry()
3190 # initialize writer setting also here to configure writer specific
3191 # settings on startup
3192 actualWriter = outputWriters[config.output['FORMAT']]
3193 writer = actualWriter("")
3196 basename = Blender.sys.basename(Blender.Get('filename'))
3198 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3200 if Blender.mode == 'background':
3201 progress = ConsoleProgressIndicator()
3202 vectorize(outputfile)
3204 progress = GraphicalProgressIndicator()
3205 Draw.Register(GUI.draw, GUI.event, GUI.button_event)