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 *
104 return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
105 # in python > 2.4 we ca use the following
106 #return [ u for u in alist if u not in locals()['_[1]'] ]
112 # We use a global progress Indicator Object
116 # Config class for global settings
120 polygons['SHOW'] = True
121 polygons['SHADING'] = 'FLAT' # FLAT or TOON
122 polygons['HSR'] = 'PAINTER' # PAINTER or NEWELL
123 # Hidden to the user for now
124 polygons['EXPANSION_TRICK'] = True
126 polygons['TOON_LEVELS'] = 2
129 edges['SHOW'] = False
130 edges['SHOW_HIDDEN'] = False
131 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
133 edges['COLOR'] = [0, 0, 0]
136 output['FORMAT'] = 'SVG'
137 output['ANIMATION'] = False
138 output['JOIN_OBJECTS'] = True
140 def saveToRegistry():
143 for k,v in config.__dict__.iteritems():
145 # config class store settings in dictionaries
146 if v.__class__ == dict().__class__:
148 regkey_prefix = k.upper()+"_"
150 for opt_k,opt_v in v.iteritems():
151 regkey = regkey_prefix + opt_k
153 registry[regkey] = opt_v
155 Blender.Registry.SetKey('VRM', registry, True)
157 saveToRegistry = staticmethod(saveToRegistry)
159 def loadFromRegistry():
160 registry = Blender.Registry.GetKey('VRM', True)
164 for k,v in registry.iteritems():
166 conf_attr = k_tmp[0].lower()
167 conf_key = str.join("_",k_tmp[1:])
170 if config.__dict__.has_key(conf_attr):
171 config.__dict__[conf_attr][conf_key] = conf_val
173 loadFromRegistry = staticmethod(loadFromRegistry)
179 def dumpfaces(flist, filename):
180 """Dump a single face to a file.
191 writerobj = SVGVectorWriter(filename)
194 writerobj._printPolygons(m)
200 sys.stderr.write(msg)
203 return (abs(v1[0]-v2[0]) < EPS and
204 abs(v1[1]-v2[1]) < EPS )
205 by_furthest_z = (lambda f1, f2:
206 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
221 # ---------------------------------------------------------------------
225 # ---------------------------------------------------------------------
231 """A utility class for HSR processing.
234 def is_nonplanar_quad(face):
235 """Determine if a quad is non-planar.
237 From: http://mathworld.wolfram.com/Coplanar.html
239 Geometric objects lying in a common plane are said to be coplanar.
240 Three noncollinear points determine a plane and so are trivially coplanar.
241 Four points are coplanar iff the volume of the tetrahedron defined by them is
247 | x_4 y_4 z_4 1 | == 0
249 Coplanarity is equivalent to the statement that the pair of lines
250 determined by the four points are not skew, and can be equivalently stated
251 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
253 An arbitrary number of n points x_1, ..., x_n can be tested for
254 coplanarity by finding the point-plane distances of the points
255 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
256 and checking if they are all zero.
257 If so, the points are all coplanar.
259 We here check only for 4-point complanarity.
265 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
269 # three points must be complanar
272 x1 = Vector(face[0].co)
273 x2 = Vector(face[1].co)
274 x3 = Vector(face[2].co)
275 x4 = Vector(face[3].co)
277 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
283 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
285 def pointInPolygon(poly, v):
288 pointInPolygon = staticmethod(pointInPolygon)
290 def edgeIntersection(s1, s2, do_perturbate=False):
292 (x1, y1) = s1[0].co[0], s1[0].co[1]
293 (x2, y2) = s1[1].co[0], s1[1].co[1]
295 (x3, y3) = s2[0].co[0], s2[0].co[1]
296 (x4, y4) = s2[1].co[0], s2[1].co[1]
304 # calculate delta values (vector components)
313 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
314 if C == 0: #/* parallel */
317 dx3 = x1 - x3 # /* combined origin offset vector */
320 a1 = (dy3 * dx2 - dx3 * dy2) / C;
321 a2 = (dy3 * dx1 - dx3 * dy1) / C;
323 # check for degeneracies
325 #print_debug(str(a1)+"\n")
326 #print_debug(str(a2)+"\n\n")
328 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
329 # Intersection on boundaries, we consider the point external?
332 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
338 return (NMesh.Vert(x, y, z), a1, a2)
341 # lines have intersections but not those segments
344 edgeIntersection = staticmethod(edgeIntersection)
346 def isVertInside(self, v):
350 # Create point at infinity
351 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
353 for i in range(len(self.v)):
354 s1 = (point_at_infinity, v)
355 s2 = (self.v[i-1], self.v[i])
357 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
360 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
364 if winding_number % 2 == 0 :
371 isVertInside = staticmethod(isVertInside)
375 return ((b[0] - a[0]) * (c[1] - a[1]) -
376 (b[1] - a[1]) * (c[0] - a[0]) )
378 det = staticmethod(det)
380 def pointInPolygon(q, P):
383 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
387 for i in range(len(P.v)):
390 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
391 if det(p0.co, p1.co, q.co) == 0 :
394 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
399 pointInPolygon = staticmethod(pointInPolygon)
401 def projectionsOverlap(f1, f2):
402 """ If you have nonconvex, but still simple polygons, an acceptable method
403 is to iterate over all vertices and perform the Point-in-polygon test[1].
404 The advantage of this method is that you can compute the exact
405 intersection point and collision normal that you will need to simulate
406 collision. When you have the point that lies inside the other polygon, you
407 just iterate over all edges of the second polygon again and look for edge
408 intersections. Note that this method detects collsion when it already
409 happens. This algorithm is fast enough to perform it hundreds of times per
412 for i in range(len(f1.v)):
415 # If a point of f1 in inside f2, there is an overlap!
417 #if HSR.isVertInside(f2, v1):
418 if HSR.pointInPolygon(v1, f2):
421 # If not the polygon can be ovelap as well, so we check for
422 # intersection between an edge of f1 and all the edges of f2
426 for j in range(len(f2.v)):
433 intrs = HSR.edgeIntersection(e1, e2)
435 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
436 # str(v2.co) + " " + str(v3.co) )
437 #print_debug("\nIntersection\n")
443 projectionsOverlap = staticmethod(projectionsOverlap)
445 def midpoint(p1, p2):
446 """Return the midpoint of two vertices.
448 m = MidpointVecs(Vector(p1), Vector(p2))
449 mv = NMesh.Vert(m[0], m[1], m[2])
453 midpoint = staticmethod(midpoint)
455 def facesplit(P, Q, facelist, nmesh):
456 """Split P or Q according to the strategy illustrated in the Newell's
460 by_furthest_z = (lambda f1, f2:
461 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
464 # Choose if split P on Q plane or vice-versa
468 d = HSR.Distance(Vector(Pi), Q)
471 pIntersectQ = (n != len(P))
475 d = HSR.Distance(Vector(Qi), P)
478 qIntersectP = (n != len(Q))
482 # 1. If parts of P lie in both half-spaces of Q
483 # then splice P in two with the plane of Q
489 newfaces = HSR.splitOn(plane, f)
491 # 2. Else if parts of Q lie in both half-space of P
492 # then splice Q in two with the plane of P
493 if qIntersectP and newfaces == None:
498 newfaces = HSR.splitOn(plane, f)
501 # 3. Else slice P in half through the mid-point of
502 # the longest pair of opposite sides
505 print "We ignore P..."
512 # v1 = midpoint(f[0], f[1])
513 # v2 = midpoint(f[1], f[2])
515 # v1 = midpoint(f[0], f[1])
516 # v2 = midpoint(f[2], f[3])
517 #vec3 = (Vector(v2)+10*Vector(f.normal))
519 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
521 #plane = NMesh.Face([v1, v2, v3])
523 #newfaces = splitOn(plane, f)
527 print "Big FAT problem, we weren't able to split POLYGONS!"
533 # if v not in plane and v in nmesh.verts:
534 # nmesh.verts.remove(v)
539 nf.col = [f.col[0]] * len(nf.v)
544 nmesh.verts.append(v)
545 # insert pieces in the list
550 # and resort the faces
551 facelist.sort(by_furthest_z)
552 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
555 #print [ f.smooth for f in facelist ]
559 facesplit = staticmethod(facesplit)
561 def isOnSegment(v1, v2, p, extremes_internal=False):
562 """Check if point p is in segment v1v2.
568 # Should we consider extreme points as internal ?
570 # if p == v1 or p == v2:
571 if l1 < EPS or l2 < EPS:
572 return extremes_internal
576 # if the sum of l1 and l2 is circa l, then the point is on segment,
577 if abs(l - (l1+l2)) < EPS:
582 isOnSegment = staticmethod(isOnSegment)
584 def Distance(point, face):
585 """ Calculate the distance between a point and a face.
587 An alternative but more expensive method can be:
589 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
590 Vector(face.no), Vector(point), 0)
592 d = Vector(ip - point).length
594 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
598 plNormal = Vector(face.no)
599 plVert0 = Vector(face.v[0])
601 d = (plVert0 * plNormal) - (p * plNormal)
603 #d = plNormal * (plVert0 - p)
605 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
609 Distance = staticmethod(Distance)
613 # make one or two new faces based on a list of vertex-indices
642 makeFaces = staticmethod(makeFaces)
644 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
645 """Split P using the plane of Q.
646 Logic taken from the knife.py python script
649 # Check if P and Q are parallel
650 u = CrossVecs(Vector(Q.no),Vector(P.no))
656 print "PARALLEL planes!!"
660 # The final aim is to find the intersection line between P
661 # and the plane of Q, and split P along this line
665 # Calculate point-plane Distance between vertices of P and plane Q
667 for i in range(0, nP):
668 d.append(HSR.Distance(P.v[i], Q))
681 #print "d0:", d0, "d1:", d1
683 # if the vertex lies in the cutplane
685 #print "d1 On cutplane"
686 posVertList.append(V1)
687 negVertList.append(V1)
689 # if the previous vertex lies in cutplane
691 #print "d0 on Cutplane"
693 #print "d1 on positive Halfspace"
694 posVertList.append(V1)
696 #print "d1 on negative Halfspace"
697 negVertList.append(V1)
699 # if they are on the same side of the plane
701 #print "On the same half-space"
703 #print "d1 on positive Halfspace"
704 posVertList.append(V1)
706 #print "d1 on negative Halfspace"
707 negVertList.append(V1)
709 # the vertices are not on the same side of the plane, so we have an intersection
711 #print "Intersection"
713 e = Vector(V0), Vector(V1)
714 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
716 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
721 #print "Intersection", inters
723 nv = NMesh.Vert(inters[0], inters[1], inters[2])
724 newVertList.append(nv)
726 posVertList.append(nv)
727 negVertList.append(nv)
730 posVertList.append(V1)
732 negVertList.append(V1)
735 # uniq for python > 2.4
736 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
737 #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
739 # a more portable way
740 posVertList = uniq(posVertList)
741 negVertList = uniq(negVertList)
744 # If vertex are all on the same half-space, return
745 #if len(posVertList) < 3:
746 # print "Problem, we created a face with less that 3 vertices??"
748 #if len(negVertList) < 3:
749 # print "Problem, we created a face with less that 3 vertices??"
752 if len(posVertList) < 3 or len(negVertList) < 3:
753 #print "RETURN NONE, SURE???"
756 if not return_positive_faces:
758 if not return_negative_faces:
761 newfaces = HSR.addNewFaces(posVertList, negVertList)
765 splitOn = staticmethod(splitOn)
767 def addNewFaces(posVertList, negVertList):
768 # Create new faces resulting from the split
770 if len(posVertList) or len(negVertList):
772 #newfaces = [posVertList] + [negVertList]
773 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
774 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
778 outfaces += HSR.makeFaces(nf)
783 addNewFaces = staticmethod(addNewFaces)
786 # ---------------------------------------------------------------------
788 ## Mesh Utility class
790 # ---------------------------------------------------------------------
794 def buildEdgeFaceUsersCache(me):
796 Takes a mesh and returns a list aligned with the meshes edges.
797 Each item is a list of the faces that use the edge
798 would be the equiv for having ed.face_users as a property
800 Taken from .blender/scripts/bpymodules/BPyMesh.py,
801 thanks to ideasman_42.
804 def sorted_edge_indicies(ed):
812 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
814 fvi= [v.index for v in f.v]# face vert idx's
815 for i in xrange(len(f)):
822 face_edges_dict[i1,i2][1].append(f)
824 face_edges= [None] * len(me.edges)
825 for ed_index, ed_faces in face_edges_dict.itervalues():
826 face_edges[ed_index]= ed_faces
830 def isMeshEdge(adjacent_faces):
833 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
834 Note: if the edge has no adjacent faces we want to show it as well,
835 useful for "edge only" portion of objects.
838 if len(adjacent_faces) == 0:
841 selected_faces = [f for f in adjacent_faces if f.sel]
843 if len(selected_faces) != 0:
848 def isSilhouetteEdge(adjacent_faces):
849 """Silhuette selection rule.
851 An edge is a silhuette edge if it is shared by two faces with
852 different selection status or if it is a boundary edge of a selected
856 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
857 (len(adjacent_faces) == 2 and
858 adjacent_faces[0].sel != adjacent_faces[1].sel)
864 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
865 isMeshEdge = staticmethod(isMeshEdge)
866 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
869 # ---------------------------------------------------------------------
871 ## Shading Utility class
873 # ---------------------------------------------------------------------
879 def toonShadingMapSetup():
880 levels = config.polygons['TOON_LEVELS']
882 texels = 2*levels - 1
883 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
889 shademap = ShadingUtils.shademap
892 shademap = ShadingUtils.toonShadingMapSetup()
895 for i in xrange(0, len(shademap)-1):
896 pivot = (shademap[i]+shademap[i+1])/2.0
901 if v < shademap[i+1]:
906 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
907 toonShading = staticmethod(toonShading)
910 # ---------------------------------------------------------------------
912 ## Projections classes
914 # ---------------------------------------------------------------------
917 """Calculate the projection of an object given the camera.
919 A projector is useful to so some per-object transformation to obtain the
920 projection of an object given the camera.
922 The main method is #doProjection# see the method description for the
926 def __init__(self, cameraObj, canvasRatio):
927 """Calculate the projection matrix.
929 The projection matrix depends, in this case, on the camera settings.
930 TAKE CARE: This projector expects vertices in World Coordinates!
933 camera = cameraObj.getData()
935 aspect = float(canvasRatio[0])/float(canvasRatio[1])
936 near = camera.clipStart
939 scale = float(camera.scale)
941 fovy = atan(0.5/aspect/(camera.lens/32))
942 fovy = fovy * 360.0/pi
945 if Blender.Get('version') < 243:
952 # What projection do we want?
953 if camera.type == camPersp:
954 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
955 elif camera.type == camOrtho:
956 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
959 # View transformation
960 cam = Matrix(cameraObj.getInverseMatrix())
965 self.projectionMatrix = mP
971 def doProjection(self, v):
972 """Project the point on the view plane.
974 Given a vertex calculate the projection using the current projection
978 # Note that we have to work on the vertex using homogeneous coordinates
979 # From blender 2.42+ we don't need to resize the vector to be 4d
980 # when applying a 4x4 matrix, but we do that anyway since we need the
981 # 4th coordinate later
982 p = self.projectionMatrix * Vector(v).resize4D()
984 # Perspective division
1001 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
1002 """Return a perspective projection matrix.
1005 top = near * tan(fovy * pi / 360.0)
1007 left = bottom*aspect
1009 x = (2.0 * near) / (right-left)
1010 y = (2.0 * near) / (top-bottom)
1011 a = (right+left) / (right-left)
1012 b = (top+bottom) / (top - bottom)
1013 c = - ((far+near) / (far-near))
1014 d = - ((2*far*near)/(far-near))
1020 [0.0, 0.0, -1.0, 0.0])
1024 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
1025 """Return an orthogonal projection matrix.
1028 # The 11 in the formula was found emiprically
1029 top = near * tan(fovy * pi / 360.0) * (scale * 11)
1031 left = bottom * aspect
1036 tx = -((right+left)/rl)
1037 ty = -((top+bottom)/tb)
1038 tz = ((far+near)/fn)
1041 [2.0/rl, 0.0, 0.0, tx],
1042 [0.0, 2.0/tb, 0.0, ty],
1043 [0.0, 0.0, 2.0/fn, tz],
1044 [0.0, 0.0, 0.0, 1.0])
1049 # ---------------------------------------------------------------------
1051 ## Progress Indicator
1053 # ---------------------------------------------------------------------
1056 """A model for a progress indicator.
1058 Do the progress calculation calculation and
1059 the view independent stuff of a progress indicator.
1061 def __init__(self, steps=0):
1067 def setSteps(self, steps):
1068 """Set the number of steps of the activity wich we want to track.
1075 def setName(self, name):
1076 """Set the name of the activity wich we want to track.
1083 def getProgress(self):
1084 return self.progress
1091 """Update the model, call this method when one step is completed.
1093 if self.progress == 100:
1097 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1098 self.progress = int(self.progress)
1103 class ProgressIndicator:
1104 """An abstraction of a View for the Progress Model
1108 # Use a refresh rate so we do not show the progress at
1109 # every update, but every 'self.refresh_rate' times.
1110 self.refresh_rate = 10
1111 self.shows_counter = 0
1115 self.progressModel = None
1117 def setQuiet(self, value):
1120 def setActivity(self, name, steps):
1121 """Initialize the Model.
1123 In a future version (with subactivities-progress support) this method
1124 could only set the current activity.
1126 self.progressModel = Progress()
1127 self.progressModel.setName(name)
1128 self.progressModel.setSteps(steps)
1130 def getActivity(self):
1131 return self.progressModel
1134 """Update the model and show the actual progress.
1136 assert(self.progressModel)
1138 if self.progressModel.update():
1142 self.show(self.progressModel.getProgress(),
1143 self.progressModel.getName())
1145 # We return always True here so we can call the update() method also
1146 # from lambda funcs (putting the call in logical AND with other ops)
1149 def show(self, progress, name=""):
1150 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1151 if self.shows_counter != 0:
1155 self.shows_counter = -1
1158 class ConsoleProgressIndicator(ProgressIndicator):
1159 """Show a progress bar on stderr, a la wget.
1162 ProgressIndicator.__init__(self)
1164 self.swirl_chars = ["-", "\\", "|", "/"]
1165 self.swirl_count = -1
1167 def show(self, progress, name):
1168 ProgressIndicator.show(self, progress, name)
1171 bar_progress = int( (progress/100.0) * bar_length )
1172 bar = ("=" * bar_progress).ljust(bar_length)
1174 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1175 swirl_char = self.swirl_chars[self.swirl_count]
1177 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1179 sys.stderr.write(progress_bar+"\r")
1181 sys.stderr.write("\n")
1184 class GraphicalProgressIndicator(ProgressIndicator):
1185 """Interface to the Blender.Window.DrawProgressBar() method.
1188 ProgressIndicator.__init__(self)
1190 #self.swirl_chars = ["-", "\\", "|", "/"]
1191 # We have to use letters with the same width, for now!
1192 # Blender progress bar considers the font widths when
1193 # calculating the progress bar width.
1194 self.swirl_chars = ["\\", "/"]
1195 self.swirl_count = -1
1197 def show(self, progress, name):
1198 ProgressIndicator.show(self, progress)
1200 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1201 swirl_char = self.swirl_chars[self.swirl_count]
1203 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1205 # Finally draw the Progress Bar
1206 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1207 Window.DrawProgressBar(progress/100.0, progress_text)
1210 Window.DrawProgressBar(1, progress_text)
1211 Window.WaitCursor(0)
1215 # ---------------------------------------------------------------------
1217 ## 2D Object representation class
1219 # ---------------------------------------------------------------------
1221 # TODO: a class to represent the needed properties of a 2D vector image
1222 # For now just using a [N]Mesh structure.
1225 # ---------------------------------------------------------------------
1227 ## Vector Drawing Classes
1229 # ---------------------------------------------------------------------
1235 A class for printing output in a vectorial format.
1237 Given a 2D representation of the 3D scene the class is responsible to
1238 write it is a vector format.
1240 Every subclasses of VectorWriter must have at last the following public
1244 - printCanvas(self, scene,
1245 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1248 def __init__(self, fileName):
1249 """Set the output file name and other properties"""
1254 config.writer = dict()
1255 config.writer['SETTING'] = True
1257 self.outputFileName = fileName
1259 context = Scene.GetCurrent().getRenderingContext()
1260 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1262 self.fps = context.fps
1266 self.animation = False
1273 def open(self, startFrame=1, endFrame=1):
1274 if startFrame != endFrame:
1275 self.startFrame = startFrame
1276 self.endFrame = endFrame
1277 self.animation = True
1279 print "Outputting to: ", self.outputFileName
1286 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1287 showHiddenEdges=False):
1288 """This is the interface for the needed printing routine.
1295 class SVGVectorWriter(VectorWriter):
1296 """A concrete class for writing SVG output.
1299 def __init__(self, fileName):
1300 """Simply call the parent Contructor.
1302 VectorWriter.__init__(self, fileName)
1311 def open(self, startFrame=1, endFrame=1):
1312 """Do some initialization operations.
1314 VectorWriter.open(self, startFrame, endFrame)
1316 self.file = open(self.outputFileName, "w")
1321 """Do some finalization operation.
1328 # remember to call the close method of the parent as last
1329 VectorWriter.close(self)
1332 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1333 showHiddenEdges=False):
1334 """Convert the scene representation to SVG.
1337 Objects = scene.objects
1339 context = scene.getRenderingContext()
1340 framenumber = context.currentFrame()
1343 framestyle = "display:none"
1345 framestyle = "display:block"
1347 # Assign an id to this group so we can set properties on it using DOM
1348 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1349 (framenumber, framestyle) )
1354 if(obj.getType() != 'Mesh'):
1357 self.file.write("<g id=\"%s\">\n" % obj.getName())
1359 mesh = obj.getData(mesh=1)
1362 self._printPolygons(mesh)
1365 self._printEdges(mesh, showHiddenEdges)
1367 self.file.write("</g>\n")
1369 self.file.write("</g>\n")
1376 def _calcCanvasCoord(self, v):
1377 """Convert vertex in scene coordinates to canvas coordinates.
1380 pt = Vector([0, 0, 0])
1382 mW = float(self.canvasSize[0])/2.0
1383 mH = float(self.canvasSize[1])/2.0
1385 # rescale to canvas size
1386 pt[0] = v.co[0]*mW + mW
1387 pt[1] = v.co[1]*mH + mH
1390 # For now we want (0,0) in the top-left corner of the canvas.
1391 # Mirror and translate along y
1393 pt[1] += self.canvasSize[1]
1397 def _printHeader(self):
1398 """Print SVG header."""
1400 self.file.write("<?xml version=\"1.0\"?>\n")
1401 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1402 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1403 self.file.write("<svg version=\"1.0\"\n")
1404 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1405 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1409 delay = 1000/self.fps
1411 self.file.write("""\n<script type="text/javascript"><![CDATA[
1412 globalStartFrame=%d;
1415 timerID = setInterval("NextFrame()", %d);
1416 globalFrameCounter=%d;
1417 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1419 self.file.write("""\n
1420 function NextFrame()
1422 currentElement = document.getElementById('frame'+globalFrameCounter)
1423 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1425 if (!currentElement)
1430 if (globalFrameCounter > globalEndFrame)
1432 clearInterval(timerID)
1438 previousElement.style.display="none";
1440 currentElement.style.display="block";
1441 globalFrameCounter++;
1447 def _printFooter(self):
1448 """Print the SVG footer."""
1450 self.file.write("\n</svg>\n")
1452 def _printPolygons(self, mesh):
1453 """Print the selected (visible) polygons.
1456 if len(mesh.faces) == 0:
1459 self.file.write("<g>\n")
1461 for face in mesh.faces:
1465 self.file.write("<path d=\"")
1467 #p = self._calcCanvasCoord(face.verts[0])
1468 p = self._calcCanvasCoord(face.v[0])
1469 self.file.write("M %g,%g L " % (p[0], p[1]))
1471 for v in face.v[1:]:
1472 p = self._calcCanvasCoord(v)
1473 self.file.write("%g,%g " % (p[0], p[1]))
1475 # get rid of the last blank space, just cosmetics here.
1476 self.file.seek(-1, 1)
1477 self.file.write(" z\"\n")
1479 # take as face color the first vertex color
1482 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1484 color = [255, 255, 255, 255]
1486 # Convert the color to the #RRGGBB form
1487 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1489 # Handle transparent polygons
1492 opacity = float(color[3])/255.0
1493 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1494 #opacity_string = "opacity: %g;" % (opacity)
1496 self.file.write("\tstyle=\"fill:" + str_col + ";")
1497 self.file.write(opacity_string)
1499 # use the stroke property to alleviate the "adjacent edges" problem,
1500 # we simulate polygon expansion using borders,
1501 # see http://www.antigrain.com/svg/index.html for more info
1504 # EXPANSION TRICK is not that useful where there is transparency
1505 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1506 # str_col = "#000000" # For debug
1507 self.file.write(" stroke:%s;\n" % str_col)
1508 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1509 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1511 self.file.write("\"/>\n")
1513 self.file.write("</g>\n")
1515 def _printEdges(self, mesh, showHiddenEdges=False):
1516 """Print the wireframe using mesh edges.
1519 stroke_width = config.edges['WIDTH']
1520 stroke_col = config.edges['COLOR']
1522 self.file.write("<g>\n")
1524 for e in mesh.edges:
1526 hidden_stroke_style = ""
1529 if showHiddenEdges == False:
1532 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1534 p1 = self._calcCanvasCoord(e.v1)
1535 p2 = self._calcCanvasCoord(e.v2)
1537 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1538 % ( p1[0], p1[1], p2[0], p2[1] ) )
1539 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1540 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1541 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1542 self.file.write(hidden_stroke_style)
1543 self.file.write("\"/>\n")
1545 self.file.write("</g>\n")
1554 SWFSupported = False
1556 class SWFVectorWriter(VectorWriter):
1557 """A concrete class for writing SWF output.
1560 def __init__(self, fileName):
1561 """Simply call the parent Contructor.
1563 VectorWriter.__init__(self, fileName)
1573 def open(self, startFrame=1, endFrame=1):
1574 """Do some initialization operations.
1576 VectorWriter.open(self, startFrame, endFrame)
1577 self.movie = SWFMovie()
1578 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1580 self.movie.setRate(self.fps)
1581 numframes = endFrame - startFrame + 1
1582 self.movie.setFrames(numframes)
1585 """Do some finalization operation.
1587 self.movie.save(self.outputFileName)
1589 # remember to call the close method of the parent
1590 VectorWriter.close(self)
1592 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1593 showHiddenEdges=False):
1594 """Convert the scene representation to SVG.
1596 context = scene.getRenderingContext()
1597 framenumber = context.currentFrame()
1599 Objects = scene.objects
1602 self.movie.remove(self.sprite)
1604 sprite = SWFSprite()
1608 if(obj.getType() != 'Mesh'):
1611 mesh = obj.getData(mesh=1)
1614 self._printPolygons(mesh, sprite)
1617 self._printEdges(mesh, sprite, showHiddenEdges)
1620 i = self.movie.add(sprite)
1621 # Remove the instance the next time
1624 self.movie.nextFrame()
1631 def _calcCanvasCoord(self, v):
1632 """Convert vertex in scene coordinates to canvas coordinates.
1635 pt = Vector([0, 0, 0])
1637 mW = float(self.canvasSize[0])/2.0
1638 mH = float(self.canvasSize[1])/2.0
1640 # rescale to canvas size
1641 pt[0] = v.co[0]*mW + mW
1642 pt[1] = v.co[1]*mH + mH
1645 # For now we want (0,0) in the top-left corner of the canvas.
1646 # Mirror and translate along y
1648 pt[1] += self.canvasSize[1]
1652 def _printPolygons(self, mesh, sprite):
1653 """Print the selected (visible) polygons.
1656 if len(mesh.faces) == 0:
1659 for face in mesh.faces:
1665 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1667 color = [255, 255, 255, 255]
1670 f = s.addFill(color[0], color[1], color[2], color[3])
1673 # The starting point of the shape
1674 p0 = self._calcCanvasCoord(face.verts[0])
1675 s.movePenTo(p0[0], p0[1])
1677 for v in face.verts[1:]:
1678 p = self._calcCanvasCoord(v)
1679 s.drawLineTo(p[0], p[1])
1682 s.drawLineTo(p0[0], p0[1])
1688 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1689 """Print the wireframe using mesh edges.
1692 stroke_width = config.edges['WIDTH']
1693 stroke_col = config.edges['COLOR']
1697 for e in mesh.edges:
1699 # Next, we set the line width and color for our shape.
1700 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1704 if showHiddenEdges == False:
1707 # SWF does not support dashed lines natively, so -for now-
1708 # draw hidden lines thinner and half-trasparent
1709 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1712 p1 = self._calcCanvasCoord(e.v1)
1713 p2 = self._calcCanvasCoord(e.v2)
1715 s.movePenTo(p1[0], p1[1])
1716 s.drawLineTo(p2[0], p2[1])
1725 from reportlab.pdfgen import canvas
1728 PDFSupported = False
1730 class PDFVectorWriter(VectorWriter):
1731 """A concrete class for writing PDF output.
1734 def __init__(self, fileName):
1735 """Simply call the parent Contructor.
1737 VectorWriter.__init__(self, fileName)
1746 def open(self, startFrame=1, endFrame=1):
1747 """Do some initialization operations.
1749 VectorWriter.open(self, startFrame, endFrame)
1750 size = (self.canvasSize[0], self.canvasSize[1])
1751 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1754 """Do some finalization operation.
1758 # remember to call the close method of the parent
1759 VectorWriter.close(self)
1761 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1762 showHiddenEdges=False):
1763 """Convert the scene representation to SVG.
1765 context = scene.getRenderingContext()
1766 framenumber = context.currentFrame()
1768 Objects = scene.objects
1772 if(obj.getType() != 'Mesh'):
1775 mesh = obj.getData(mesh=1)
1778 self._printPolygons(mesh)
1781 self._printEdges(mesh, showHiddenEdges)
1783 self.canvas.showPage()
1789 def _calcCanvasCoord(self, v):
1790 """Convert vertex in scene coordinates to canvas coordinates.
1793 pt = Vector([0, 0, 0])
1795 mW = float(self.canvasSize[0])/2.0
1796 mH = float(self.canvasSize[1])/2.0
1798 # rescale to canvas size
1799 pt[0] = v.co[0]*mW + mW
1800 pt[1] = v.co[1]*mH + mH
1803 # For now we want (0,0) in the top-left corner of the canvas.
1804 # Mirror and translate along y
1806 pt[1] += self.canvasSize[1]
1810 def _printPolygons(self, mesh):
1811 """Print the selected (visible) polygons.
1814 if len(mesh.faces) == 0:
1817 for face in mesh.faces:
1823 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1826 color = [1, 1, 1, 1]
1828 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1830 self.canvas.setStrokeColorRGB(0, 0, 0)
1832 path = self.canvas.beginPath()
1834 # The starting point of the path
1835 p0 = self._calcCanvasCoord(face.verts[0])
1836 path.moveTo(p0[0], p0[1])
1838 for v in face.verts[1:]:
1839 p = self._calcCanvasCoord(v)
1840 path.lineTo(p[0], p[1])
1845 self.canvas.drawPath(path, stroke=0, fill=1)
1847 def _printEdges(self, mesh, showHiddenEdges=False):
1848 """Print the wireframe using mesh edges.
1851 stroke_width = config.edges['WIDTH']
1852 stroke_col = config.edges['COLOR']
1854 self.canvas.setLineCap(1)
1855 self.canvas.setLineJoin(1)
1856 self.canvas.setLineWidth(stroke_width)
1857 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1860 for e in mesh.edges:
1862 self.canvas.setLineWidth(stroke_width)
1865 if showHiddenEdges == False:
1868 # PDF does not support dashed lines natively, so -for now-
1869 # draw hidden lines thinner
1870 self.canvas.setLineWidth(stroke_width/2.0)
1872 p1 = self._calcCanvasCoord(e.v1)
1873 p2 = self._calcCanvasCoord(e.v2)
1875 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1879 # ---------------------------------------------------------------------
1881 ## Rendering Classes
1883 # ---------------------------------------------------------------------
1885 # A dictionary to collect different shading style methods
1886 shadingStyles = dict()
1887 shadingStyles['FLAT'] = None
1888 shadingStyles['TOON'] = None
1890 # A dictionary to collect different edge style methods
1892 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1893 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1895 # A dictionary to collect the supported output formats
1896 outputWriters = dict()
1897 outputWriters['SVG'] = SVGVectorWriter
1899 outputWriters['SWF'] = SWFVectorWriter
1901 outputWriters['PDF'] = PDFVectorWriter
1905 """Render a scene viewed from the active camera.
1907 This class is responsible of the rendering process, transformation and
1908 projection of the objects in the scene are invoked by the renderer.
1910 The rendering is done using the active camera for the current scene.
1914 """Make the rendering process only for the current scene by default.
1916 We will work on a copy of the scene, to be sure that the current scene do
1917 not get modified in any way.
1920 # Render the current Scene, this should be a READ-ONLY property
1921 self._SCENE = Scene.GetCurrent()
1923 # Use the aspect ratio of the scene rendering context
1924 context = self._SCENE.getRenderingContext()
1926 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1927 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1928 float(context.aspectRatioY())
1931 # Render from the currently active camera
1932 #self.cameraObj = self._SCENE.objects.camera
1941 def doRendering(self, outputWriter, animation=False):
1942 """Render picture or animation and write it out.
1945 - a Vector writer object that will be used to output the result.
1946 - a flag to tell if we want to render an animation or only the
1950 context = self._SCENE.getRenderingContext()
1951 origCurrentFrame = context.currentFrame()
1953 # Handle the animation case
1955 startFrame = origCurrentFrame
1956 endFrame = startFrame
1959 startFrame = context.startFrame()
1960 endFrame = context.endFrame()
1961 outputWriter.open(startFrame, endFrame)
1963 # Do the rendering process frame by frame
1964 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1965 for f in xrange(startFrame, endFrame+1):
1966 print "\n\nFrame: %d" % f
1968 # FIXME To get the correct camera position we have to use +1 here.
1969 # Is there a bug somewhere in the Scene module?
1970 context.currentFrame(f+1)
1971 self.cameraObj = self._SCENE.objects.camera
1973 # Use some temporary workspace, a full copy of the scene
1974 inputScene = self._SCENE.copy(2)
1976 # To get the objects at this frame remove the +1 ...
1977 ctx = inputScene.getRenderingContext()
1981 # Get a projector for this camera.
1982 # NOTE: the projector wants object in world coordinates,
1983 # so we should remember to apply modelview transformations
1984 # _before_ we do projection transformations.
1985 self.proj = Projector(self.cameraObj, self.canvasRatio)
1988 renderedScene = self.doRenderScene(inputScene)
1990 print "There was an error! Aborting."
1992 print traceback.print_exc()
1994 self._SCENE.makeCurrent()
1995 Scene.Unlink(inputScene)
1999 outputWriter.printCanvas(renderedScene,
2000 doPrintPolygons = config.polygons['SHOW'],
2001 doPrintEdges = config.edges['SHOW'],
2002 showHiddenEdges = config.edges['SHOW_HIDDEN'])
2004 # delete the rendered scene
2005 self._SCENE.makeCurrent()
2006 Scene.Unlink(renderedScene)
2009 outputWriter.close()
2011 context.currentFrame(origCurrentFrame)
2014 def doRenderScene(self, workScene):
2015 """Control the rendering process.
2017 Here we control the entire rendering process invoking the operation
2018 needed to transform and project the 3D scene in two dimensions.
2021 # global processing of the scene
2023 self._filterHiddenObjects(workScene)
2025 self._buildLightSetup(workScene)
2027 self._doSceneClipping(workScene)
2029 self._doConvertGeometricObjsToMesh(workScene)
2031 if config.output['JOIN_OBJECTS']:
2032 self._joinMeshObjectsInScene(workScene)
2034 self._doSceneDepthSorting(workScene)
2036 # Per object activities
2038 Objects = workScene.objects
2040 print "Total Objects: %d" % len(Objects)
2041 for i,obj in enumerate(Objects):
2043 print "Rendering Object: %d" % i
2045 if obj.getType() != 'Mesh':
2046 print "Only Mesh supported! - Skipping type:", obj.getType()
2049 print "Rendering: ", obj.getName()
2051 mesh = obj.getData(mesh=1)
2053 self._doModelingTransformation(mesh, obj.matrix)
2055 self._doBackFaceCulling(mesh)
2058 # When doing HSR with NEWELL we may want to flip all normals
2060 if config.polygons['HSR'] == "NEWELL":
2061 for f in mesh.faces:
2064 for f in mesh.faces:
2067 self._doLighting(mesh)
2069 # Do "projection" now so we perform further processing
2070 # in Normalized View Coordinates
2071 self._doProjection(mesh, self.proj)
2073 self._doViewFrustumClipping(mesh)
2075 self._doHiddenSurfaceRemoval(mesh)
2077 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2079 # Update the object data, important! :)
2091 def _getObjPosition(self, obj):
2092 """Return the obj position in World coordinates.
2094 return obj.matrix.translationPart()
2096 def _cameraViewVector(self):
2097 """Get the View Direction form the camera matrix.
2099 return Vector(self.cameraObj.matrix[2]).resize3D()
2104 def _isFaceVisible(self, face):
2105 """Determine if a face of an object is visible from the current camera.
2107 The view vector is calculated from the camera location and one of the
2108 vertices of the face (expressed in World coordinates, after applying
2109 modelview transformations).
2111 After those transformations we determine if a face is visible by
2112 computing the angle between the face normal and the view vector, this
2113 angle has to be between -90 and 90 degrees for the face to be visible.
2114 This corresponds somehow to the dot product between the two, if it
2115 results > 0 then the face is visible.
2117 There is no need to normalize those vectors since we are only interested in
2118 the sign of the cross product and not in the product value.
2120 NOTE: here we assume the face vertices are in WorldCoordinates, so
2121 please transform the object _before_ doing the test.
2124 normal = Vector(face.no)
2125 camPos = self._getObjPosition(self.cameraObj)
2128 # View Vector in orthographics projections is the view Direction of
2130 if self.cameraObj.data.getType() == 1:
2131 view_vect = self._cameraViewVector()
2133 # View vector in perspective projections can be considered as
2134 # the difference between the camera position and one point of
2135 # the face, we choose the farthest point from the camera.
2136 if self.cameraObj.data.getType() == 0:
2137 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2141 # if d > 0 the face is visible from the camera
2142 d = view_vect * normal
2152 def _filterHiddenObjects(self, scene):
2153 """Discard object that are on hidden layers in the scene.
2156 Objects = scene.objects
2158 visible_obj_list = [ obj for obj in Objects if
2159 set(obj.layers).intersection(set(scene.getLayers())) ]
2162 if o not in visible_obj_list:
2163 scene.objects.unlink(o)
2169 def _buildLightSetup(self, scene):
2170 # Get the list of lighting sources
2171 obj_lst = scene.objects
2172 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp' ]
2174 # When there are no lights we use a default lighting source
2175 # that have the same position of the camera
2176 if len(self.lights) == 0:
2177 l = Lamp.New('Lamp')
2178 lobj = Object.New('Lamp')
2179 lobj.loc = self.cameraObj.loc
2181 self.lights.append(lobj)
2184 def _doSceneClipping(self, scene):
2185 """Clip whole objects against the View Frustum.
2187 For now clip away only objects according to their center position.
2190 cam_pos = self._getObjPosition(self.cameraObj)
2191 view_vect = self._cameraViewVector()
2193 near = self.cameraObj.data.clipStart
2194 far = self.cameraObj.data.clipEnd
2196 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2197 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2198 fovy = fovy * 360.0/pi
2200 Objects = scene.objects
2203 if o.getType() != 'Mesh': continue;
2206 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2208 d = obj_vect*view_vect
2209 theta = AngleBetweenVecs(obj_vect, view_vect)
2211 # if the object is outside the view frustum, clip it away
2212 if (d < near) or (d > far) or (theta > fovy):
2213 scene.objects.unlink(o)
2216 # Use the object bounding box
2217 # (whose points are already in WorldSpace Coordinate)
2219 bb = o.getBoundBox()
2223 p_vect = Vector(cam_pos) - Vector(p)
2225 d = p_vect * view_vect
2226 theta = AngleBetweenVecs(p_vect, view_vect)
2228 # Is this point outside the view frustum?
2229 if (d < near) or (d > far) or (theta > fovy):
2232 # If the bb is all outside the view frustum we clip the whole
2234 if points_outside == len(bb):
2235 scene.objects.unlink(o)
2239 def _doConvertGeometricObjsToMesh(self, scene):
2240 """Convert all "geometric" objects to mesh ones.
2242 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2243 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2245 Objects = scene.objects
2247 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2250 obj = self._convertToRawMeshObj(obj)
2251 scene.objects.link(obj)
2252 scene.objects.unlink(old_obj)
2255 # XXX Workaround for Text and Curve which have some normals
2256 # inverted when they are converted to Mesh, REMOVE that when
2257 # blender will fix that!!
2258 if old_obj.getType() in ['Curve', 'Text']:
2259 me = obj.getData(mesh=1)
2260 for f in me.faces: f.sel = 1;
2261 for v in me.verts: v.sel = 1;
2268 def _doSceneDepthSorting(self, scene):
2269 """Sort objects in the scene.
2271 The object sorting is done accordingly to the object centers.
2274 c = self._getObjPosition(self.cameraObj)
2276 by_obj_center_pos = (lambda o1, o2:
2277 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2278 cmp((self._getObjPosition(o1) - Vector(c)).length,
2279 (self._getObjPosition(o2) - Vector(c)).length)
2282 # Implement sorting by bounding box, the object with the bb
2283 # nearest to the camera should be drawn as last.
2284 by_nearest_bbox_point = (lambda o1, o2:
2285 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2286 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2287 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2292 Objects = list(scene.objects)
2294 #Objects.sort(by_obj_center_pos)
2295 Objects.sort(by_nearest_bbox_point)
2299 scene.objects.unlink(o)
2300 scene.objects.link(o)
2302 def _joinMeshObjectsInScene(self, scene):
2303 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2306 oList = [o for o in scene.objects if o.getType()=='Mesh']
2308 # FIXME: Object.join() do not work if the list contains 1 object
2312 mesh = Mesh.New('BigOne')
2313 bigObj = Object.New('Mesh', 'BigOne')
2316 scene.objects.link(bigObj)
2320 except RuntimeError:
2321 print "\nWarning! - Can't Join Objects\n"
2322 scene.objects.unlink(bigObj)
2325 print "Objects Type error?"
2328 scene.objects.unlink(o)
2333 # Per object/mesh methods
2335 def _convertToRawMeshObj(self, object):
2336 """Convert geometry based object to a mesh object.
2338 me = Mesh.New('RawMesh_'+object.name)
2339 me.getFromObject(object.name)
2341 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2344 # If the object has no materials set a default material
2345 if not me.materials:
2346 me.materials = [Material.New()]
2347 #for f in me.faces: f.mat = 0
2349 newObject.setMatrix(object.getMatrix())
2353 def _doModelingTransformation(self, mesh, matrix):
2354 """Transform object coordinates to world coordinates.
2356 This step is done simply applying to the object its tranformation
2357 matrix and recalculating its normals.
2359 # XXX FIXME: blender do not transform normals in the right way when
2360 # there are negative scale values
2361 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2362 print "WARNING: Negative scales, expect incorrect results!"
2364 mesh.transform(matrix, True)
2366 def _doBackFaceCulling(self, mesh):
2367 """Simple Backface Culling routine.
2369 At this level we simply do a visibility test face by face and then
2370 select the vertices belonging to visible faces.
2373 # Select all vertices, so edges can be displayed even if there are no
2375 for v in mesh.verts:
2378 Mesh.Mode(Mesh.SelectModes['FACE'])
2380 for f in mesh.faces:
2382 if self._isFaceVisible(f):
2385 def _doLighting(self, mesh):
2386 """Apply an Illumination and shading model to the object.
2388 The model used is the Phong one, it may be inefficient,
2389 but I'm just learning about rendering and starting from Phong seemed
2390 the most natural way.
2393 # If the mesh has vertex colors already, use them,
2394 # otherwise turn them on and do some calculations
2395 if mesh.vertexColors:
2397 mesh.vertexColors = 1
2399 materials = mesh.materials
2401 camPos = self._getObjPosition(self.cameraObj)
2403 # We do per-face color calculation (FLAT Shading), we can easily turn
2404 # to a per-vertex calculation if we want to implement some shading
2405 # technique. For an example see:
2406 # http://www.miralab.unige.ch/papers/368.pdf
2407 for f in mesh.faces:
2413 mat = materials[f.mat]
2415 # A new default material
2417 mat = Material.New('defMat')
2419 # Check if it is a shadeless material
2420 elif mat.getMode() & Material.Modes['SHADELESS']:
2422 # Convert to a value between 0 and 255
2423 tmp_col = [ int(c * 255.0) for c in I]
2434 # do vertex color calculation
2436 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2438 for l in self.lights:
2440 light_pos = self._getObjPosition(l)
2441 light = light_obj.getData()
2443 L = Vector(light_pos).normalize()
2445 V = (Vector(camPos) - Vector(f.cent)).normalize()
2447 N = Vector(f.no).normalize()
2449 if config.polygons['SHADING'] == 'TOON':
2450 NL = ShadingUtils.toonShading(N*L)
2454 # Should we use NL instead of (N*L) here?
2455 R = 2 * (N*L) * N - L
2457 Ip = light.getEnergy()
2459 # Diffuse co-efficient
2460 kd = mat.getRef() * Vector(mat.getRGBCol())
2462 kd[i] *= light.col[i]
2464 Idiff = Ip * kd * max(0, NL)
2467 # Specular component
2468 ks = mat.getSpec() * Vector(mat.getSpecCol())
2469 ns = mat.getHardness()
2470 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2472 TotDiffSpec += (Idiff+Ispec)
2476 Iamb = Vector(Blender.World.Get()[0].getAmb())
2479 # Emissive component (convert to a triplet)
2480 ki = Vector([mat.getEmit()]*3)
2482 #I = ki + Iamb + (Idiff + Ispec)
2483 I = ki + (ka * Iamb) + TotDiffSpec
2486 # Set Alpha component
2488 I.append(mat.getAlpha())
2490 # Clamp I values between 0 and 1
2491 I = [ min(c, 1) for c in I]
2492 I = [ max(0, c) for c in I]
2494 # Convert to a value between 0 and 255
2495 tmp_col = [ int(c * 255.0) for c in I]
2503 def _doProjection(self, mesh, projector):
2504 """Apply Viewing and Projection tranformations.
2507 for v in mesh.verts:
2508 p = projector.doProjection(v.co[:])
2513 #mesh.recalcNormals()
2516 # We could reeset Camera matrix, since now
2517 # we are in Normalized Viewing Coordinates,
2518 # but doung that would affect World Coordinate
2519 # processing for other objects
2521 #self.cameraObj.data.type = 1
2522 #self.cameraObj.data.scale = 2.0
2523 #m = Matrix().identity()
2524 #self.cameraObj.setMatrix(m)
2526 def _doViewFrustumClipping(self, mesh):
2527 """Clip faces against the View Frustum.
2530 # The Canonical View Volume, 8 vertices, and 6 faces,
2531 # We consider its face normals pointing outside
2533 v1 = NMesh.Vert(1, 1, -1)
2534 v2 = NMesh.Vert(1, -1, -1)
2535 v3 = NMesh.Vert(-1, -1, -1)
2536 v4 = NMesh.Vert(-1, 1, -1)
2537 v5 = NMesh.Vert(1, 1, 1)
2538 v6 = NMesh.Vert(1, -1, 1)
2539 v7 = NMesh.Vert(-1, -1, 1)
2540 v8 = NMesh.Vert(-1, 1, 1)
2543 f1 = NMesh.Face([v1, v4, v3, v2])
2545 f2 = NMesh.Face([v5, v6, v7, v8])
2547 f3 = NMesh.Face([v1, v2, v6, v5])
2549 f4 = NMesh.Face([v2, v3, v7, v6])
2551 f5 = NMesh.Face([v3, v4, v8, v7])
2553 f6 = NMesh.Face([v4, v1, v5, v8])
2556 nmesh = NMesh.GetRaw(mesh.name)
2557 clippedfaces = nmesh.faces[:]
2558 facelist = clippedfaces[:]
2560 for clipface in cvv:
2566 #newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2570 # Check if the face is all outside the view frustum
2571 # TODO: Do this test before, it is more efficient
2574 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2577 if points_outside != len(f):
2578 clippedfaces.append(f)
2582 nmesh.verts.append(v)
2586 nf.col = [f.col[0]] * len(nf.v)
2588 clippedfaces.append(nf)
2589 facelist = clippedfaces[:]
2592 nmesh.faces = facelist
2597 def __simpleDepthSort(self, mesh):
2598 """Sort faces by the furthest vertex.
2600 This simple mesthod is known also as the painter algorithm, and it
2601 solves HSR correctly only for convex meshes.
2606 # The sorting requires circa n*log(n) steps
2608 progress.setActivity("HSR: Painter", n*log(n))
2610 by_furthest_z = (lambda f1, f2: progress.update() and
2611 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2614 # FIXME: using NMesh to sort faces. We should avoid that!
2615 nmesh = NMesh.GetRaw(mesh.name)
2617 # remember that _higher_ z values mean further points
2618 nmesh.faces.sort(by_furthest_z)
2619 nmesh.faces.reverse()
2624 def __newellDepthSort(self, mesh):
2625 """Newell's depth sorting.
2631 # Find non planar quads and convert them to triangle
2632 #for f in mesh.faces:
2634 # if is_nonplanar_quad(f.v):
2635 # print "NON QUAD??"
2639 # Now reselect all faces
2640 for f in mesh.faces:
2642 mesh.quadToTriangle()
2644 # FIXME: using NMesh to sort faces. We should avoid that!
2645 nmesh = NMesh.GetRaw(mesh.name)
2647 # remember that _higher_ z values mean further points
2648 nmesh.faces.sort(by_furthest_z)
2649 nmesh.faces.reverse()
2651 # Begin depth sort tests
2653 # use the smooth flag to set marked faces
2654 for f in nmesh.faces:
2657 facelist = nmesh.faces[:]
2661 # The steps are _at_least_ equal to len(facelist), we do not count the
2662 # feces coming out from splitting!!
2663 progress.setActivity("HSR: Newell", len(facelist))
2664 #progress.setQuiet(True)
2667 while len(facelist):
2668 debug("\n----------------------\n")
2669 debug("len(facelits): %d\n" % len(facelist))
2672 pSign = sign(P.normal[2])
2674 # We can discard faces parallel to the view vector
2675 #if P.normal[2] == 0:
2676 # facelist.remove(P)
2682 for Q in facelist[1:]:
2684 debug("P.smooth: " + str(P.smooth) + "\n")
2685 debug("Q.smooth: " + str(Q.smooth) + "\n")
2688 qSign = sign(Q.normal[2])
2689 # TODO: check also if Q is parallel??
2691 # Test 0: We need to test only those Qs whose furthest vertex
2692 # is closer to the observer than the closest vertex of P.
2694 zP = [v.co[2] for v in P.v]
2695 zQ = [v.co[2] for v in Q.v]
2696 notZOverlap = min(zP) > max(zQ) + EPS
2700 debug("NOT Z OVERLAP!\n")
2702 # If Q is not marked then we can safely print P
2705 debug("met a marked face\n")
2709 # Test 1: X extent overlapping
2710 xP = [v.co[0] for v in P.v]
2711 xQ = [v.co[0] for v in Q.v]
2712 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2713 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2717 debug("NOT X OVERLAP!\n")
2721 # Test 2: Y extent Overlapping
2722 yP = [v.co[1] for v in P.v]
2723 yQ = [v.co[1] for v in Q.v]
2724 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2725 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2729 debug("NOT Y OVERLAP!\n")
2733 # Test 3: P vertices are all behind the plane of Q
2736 d = qSign * HSR.Distance(Vector(Pi), Q)
2739 pVerticesBehindPlaneQ = (n == len(P))
2741 if pVerticesBehindPlaneQ:
2743 debug("P BEHIND Q!\n")
2747 # Test 4: Q vertices in front of the plane of P
2750 d = pSign * HSR.Distance(Vector(Qi), P)
2753 qVerticesInFrontPlaneP = (n == len(Q))
2755 if qVerticesInFrontPlaneP:
2757 debug("Q IN FRONT OF P!\n")
2761 # Test 5: Check if projections of polygons effectively overlap,
2762 # in previous tests we checked only bounding boxes.
2764 #if not projectionsOverlap(P, Q):
2765 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2767 debug("Projections do not overlap!\n")
2770 # We still can't say if P obscures Q.
2772 # But if Q is marked we do a face-split trying to resolve a
2773 # difficulty (maybe a visibility cycle).
2776 debug("Possibly a cycle detected!\n")
2777 debug("Split here!!\n")
2779 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2783 # The question now is: Does Q obscure P?
2786 # Test 3bis: Q vertices are all behind the plane of P
2789 d = pSign * HSR.Distance(Vector(Qi), P)
2792 qVerticesBehindPlaneP = (n == len(Q))
2794 if qVerticesBehindPlaneP:
2795 debug("\nTest 3bis\n")
2796 debug("Q BEHIND P!\n")
2799 # Test 4bis: P vertices in front of the plane of Q
2802 d = qSign * HSR.Distance(Vector(Pi), Q)
2805 pVerticesInFrontPlaneQ = (n == len(P))
2807 if pVerticesInFrontPlaneQ:
2808 debug("\nTest 4bis\n")
2809 debug("P IN FRONT OF Q!\n")
2812 # We don't even know if Q does obscure P, so they should
2813 # intersect each other, split one of them in two parts.
2814 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2815 debug("\nSimple Intersection?\n")
2816 debug("Test 3bis or 4bis failed\n")
2817 debug("Split here!!2\n")
2819 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2824 facelist.insert(0, Q)
2827 debug("Q marked!\n")
2831 if split_done == 0 and face_marked == 0:
2834 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2838 if len(facelist) == 870:
2839 dumpfaces([P, Q], "loopdebug.svg")
2842 #if facelist == None:
2844 # print [v.co for v in P]
2845 # print [v.co for v in Q]
2848 # end of while len(facelist)
2851 nmesh.faces = maplist
2852 #for f in nmesh.faces:
2858 def _doHiddenSurfaceRemoval(self, mesh):
2859 """Do HSR for the given mesh.
2861 if len(mesh.faces) == 0:
2864 if config.polygons['HSR'] == 'PAINTER':
2865 print "\nUsing the Painter algorithm for HSR."
2866 self.__simpleDepthSort(mesh)
2868 elif config.polygons['HSR'] == 'NEWELL':
2869 print "\nUsing the Newell's algorithm for HSR."
2870 self.__newellDepthSort(mesh)
2873 def _doEdgesStyle(self, mesh, edgestyleSelect):
2874 """Process Mesh Edges accroding to a given selection style.
2876 Examples of algorithms:
2879 given an edge if its adjacent faces have the same normal (that is
2880 they are complanar), than deselect it.
2883 given an edge if one its adjacent faces is frontfacing and the
2884 other is backfacing, than select it, else deselect.
2887 Mesh.Mode(Mesh.SelectModes['EDGE'])
2889 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2891 for i,edge_faces in enumerate(edge_cache):
2892 mesh.edges[i].sel = 0
2893 if edgestyleSelect(edge_faces):
2894 mesh.edges[i].sel = 1
2897 for e in mesh.edges:
2900 if edgestyleSelect(e, mesh):
2906 # ---------------------------------------------------------------------
2908 ## GUI Class and Main Program
2910 # ---------------------------------------------------------------------
2913 from Blender import BGL, Draw
2914 from Blender.BGL import *
2920 # Output Format menu
2921 output_format = config.output['FORMAT']
2922 default_value = outputWriters.keys().index(output_format)+1
2923 GUI.outFormatMenu = Draw.Create(default_value)
2924 GUI.evtOutFormatMenu = 0
2926 # Animation toggle button
2927 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2928 GUI.evtAnimToggle = 1
2930 # Join Objects toggle button
2931 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2932 GUI.evtJoinObjsToggle = 2
2934 # Render filled polygons
2935 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2937 # Shading Style menu
2938 shading_style = config.polygons['SHADING']
2939 default_value = shadingStyles.keys().index(shading_style)+1
2940 GUI.shadingStyleMenu = Draw.Create(default_value)
2941 GUI.evtShadingStyleMenu = 21
2943 GUI.evtPolygonsToggle = 3
2944 # We hide the config.polygons['EXPANSION_TRICK'], for now
2946 # Render polygon edges
2947 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2948 GUI.evtShowEdgesToggle = 4
2950 # Render hidden edges
2951 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2952 GUI.evtShowHiddenEdgesToggle = 5
2955 edge_style = config.edges['STYLE']
2956 default_value = edgeStyles.keys().index(edge_style)+1
2957 GUI.edgeStyleMenu = Draw.Create(default_value)
2958 GUI.evtEdgeStyleMenu = 6
2961 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2962 GUI.evtEdgeWidthSlider = 7
2965 c = config.edges['COLOR']
2966 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2967 GUI.evtEdgeColorPicker = 71
2970 GUI.evtRenderButton = 8
2973 GUI.evtExitButton = 9
2975 # Save default button
2976 GUI.evtSaveDefaultButton = 99
2980 # initialize static members
2983 glClear(GL_COLOR_BUFFER_BIT)
2984 glColor3f(0.0, 0.0, 0.0)
2985 glRasterPos2i(10, 380)
2986 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2988 glRasterPos2i(10, 365)
2989 Draw.Text("%s (c) 2006, 2007" % __author__)
2991 glRasterPos2i(10, 335)
2992 Draw.Text("Press Q or ESC to quit.")
2994 # Build the output format menu
2995 glRasterPos2i(10, 310)
2996 Draw.Text("Select the output Format:")
2997 outMenuStruct = "Output Format %t"
2998 for t in outputWriters.keys():
2999 outMenuStruct = outMenuStruct + "|%s" % t
3000 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
3001 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
3004 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
3005 10, 260, 160, 18, GUI.animToggle.val,
3006 "Toggle rendering of animations")
3008 # Join Objects toggle
3009 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
3010 10, 235, 160, 18, GUI.joinObjsToggle.val,
3011 "Join objects in the rendered file")
3014 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
3016 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
3018 Draw.Button("Save settings as default", GUI.evtSaveDefaultButton, 10, 210-50, 160, 18,
3019 "Save settings as default")
3022 glRasterPos2i(200, 310)
3023 Draw.Text("Rendering Style:")
3026 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
3027 200, 285, 160, 18, GUI.polygonsToggle.val,
3028 "Render filled polygons")
3030 if GUI.polygonsToggle.val == 1:
3032 # Polygon Shading Style
3033 shadingStyleMenuStruct = "Shading Style %t"
3034 for t in shadingStyles.keys():
3035 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
3036 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
3037 200, 260, 160, 18, GUI.shadingStyleMenu.val,
3038 "Choose the shading style")
3042 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
3043 200, 235, 160, 18, GUI.showEdgesToggle.val,
3044 "Render polygon edges")
3046 if GUI.showEdgesToggle.val == 1:
3049 edgeStyleMenuStruct = "Edge Style %t"
3050 for t in edgeStyles.keys():
3051 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
3052 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
3053 200, 210, 160, 18, GUI.edgeStyleMenu.val,
3054 "Choose the edge style")
3057 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
3058 200, 185, 140, 18, GUI.edgeWidthSlider.val,
3059 0.0, 10.0, 0, "Change Edge Width")
3062 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
3063 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
3066 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
3067 GUI.evtShowHiddenEdgesToggle,
3068 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
3069 "Render hidden edges as dashed lines")
3072 def event(evt, val):
3074 if evt == Draw.ESCKEY or evt == Draw.QKEY:
3081 def button_event(evt):
3083 if evt == GUI.evtExitButton:
3086 elif evt == GUI.evtOutFormatMenu:
3087 i = GUI.outFormatMenu.val - 1
3088 config.output['FORMAT']= outputWriters.keys()[i]
3089 # Set the new output file
3091 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3093 elif evt == GUI.evtAnimToggle:
3094 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3096 elif evt == GUI.evtJoinObjsToggle:
3097 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3099 elif evt == GUI.evtPolygonsToggle:
3100 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3102 elif evt == GUI.evtShadingStyleMenu:
3103 i = GUI.shadingStyleMenu.val - 1
3104 config.polygons['SHADING'] = shadingStyles.keys()[i]
3106 elif evt == GUI.evtShowEdgesToggle:
3107 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3109 elif evt == GUI.evtShowHiddenEdgesToggle:
3110 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3112 elif evt == GUI.evtEdgeStyleMenu:
3113 i = GUI.edgeStyleMenu.val - 1
3114 config.edges['STYLE'] = edgeStyles.keys()[i]
3116 elif evt == GUI.evtEdgeWidthSlider:
3117 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3119 elif evt == GUI.evtEdgeColorPicker:
3120 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3122 elif evt == GUI.evtRenderButton:
3123 label = "Save %s" % config.output['FORMAT']
3124 # Show the File Selector
3126 Blender.Window.FileSelector(vectorize, label, outputfile)
3128 elif evt == GUI.evtSaveDefaultButton:
3129 config.saveToRegistry()
3132 print "Event: %d not handled!" % evt
3139 from pprint import pprint
3141 pprint(config.output)
3142 pprint(config.polygons)
3143 pprint(config.edges)
3145 _init = staticmethod(_init)
3146 draw = staticmethod(draw)
3147 event = staticmethod(event)
3148 button_event = staticmethod(button_event)
3149 conf_debug = staticmethod(conf_debug)
3151 # A wrapper function for the vectorizing process
3152 def vectorize(filename):
3153 """The vectorizing process is as follows:
3155 - Instanciate the writer and the renderer
3160 print "\nERROR: invalid file name!"
3163 from Blender import Window
3164 editmode = Window.EditMode()
3165 if editmode: Window.EditMode(0)
3167 actualWriter = outputWriters[config.output['FORMAT']]
3168 writer = actualWriter(filename)
3170 renderer = Renderer()
3171 renderer.doRendering(writer, config.output['ANIMATION'])
3173 if editmode: Window.EditMode(1)
3178 if __name__ == "__main__":
3182 config.loadFromRegistry()
3184 # initialize writer setting also here to configure writer specific
3185 # settings on startup
3186 actualWriter = outputWriters[config.output['FORMAT']]
3187 writer = actualWriter("")
3190 basename = Blender.sys.basename(Blender.Get('filename'))
3192 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3194 if Blender.mode == 'background':
3195 progress = ConsoleProgressIndicator()
3196 vectorize(outputfile)
3198 progress = GraphicalProgressIndicator()
3199 Draw.Register(GUI.draw, GUI.event, GUI.button_event)