6 Tooltip: 'Vector Rendering Method script'
9 __author__ = "Antonio Ospite"
10 __url__ = ["http://projects.blender.org/projects/vrm"]
11 __version__ = "0.3.beta"
14 Render the scene and save the result in vector format.
17 # ---------------------------------------------------------------------
18 # Copyright (c) 2006 Antonio Ospite
20 # This program is free software; you can redistribute it and/or modify
21 # it under the terms of the GNU General Public License as published by
22 # the Free Software Foundation; either version 2 of the License, or
23 # (at your option) any later version.
25 # This program is distributed in the hope that it will be useful,
26 # but WITHOUT ANY WARRANTY; without even the implied warranty of
27 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 # GNU General Public License for more details.
30 # You should have received a copy of the GNU General Public License
31 # along with this program; if not, write to the Free Software
32 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
34 # ---------------------------------------------------------------------
37 # Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
38 # Thanks to Nikola Radovanovic, the author of the original VRM script,
39 # the code you read here has been rewritten _almost_ entirely
40 # from scratch but Nikola gave me the idea, so I thank him publicly.
42 # ---------------------------------------------------------------------
44 # Things TODO for a next release:
45 # - FIX the issue with negative scales in object tranformations!
46 # - Use a better depth sorting algorithm
47 # - Review how selections are made (this script uses selection states of
48 # primitives to represent visibility infos)
49 # - Use a data structure other than Mesh to represent the 2D image?
50 # Think to a way to merge (adjacent) polygons that have the same color.
51 # Or a way to use paths for silhouettes and contours.
52 # - Consider SMIL for animation handling instead of ECMA Script? (Firefox do
53 # not support SMIL for animations)
54 # - Switch to the Mesh structure, should be considerably faster
55 # (partially done, but with Mesh we cannot sort faces, yet)
56 # - Implement Edge Styles (silhouettes, contours, etc.) (partially done).
57 # - Implement Shading Styles? (partially done, to make more flexible).
58 # - Add Vector Writers other than SVG.
59 # - set the background color!
60 # - Check memory use!!
62 # ---------------------------------------------------------------------
67 # * First release after code restucturing.
68 # Now the script offers a useful set of functionalities
69 # and it can render animations, too.
70 # * Optimization in Renderer.doEdgeStyle(), build a topology cache
71 # so to speed up the lookup of adjacent faces of an edge.
73 # * The SVG output is now SVG 1.0 valid.
74 # Checked with: http://jiggles.w3.org/svgvalidator/ValidatorURI.html
75 # * Progress indicator during HSR.
76 # * Initial SWF output support (using ming)
77 # * Fixed a bug in the animation code, now the projection matrix is
78 # recalculated at each frame!
79 # * PDF output (using reportlab)
80 # * Fixed another problem in the animation code the current frame was off
81 # by one in the case of camera movement.
82 # * Use fps as specified in blender when VectorWriter handles animation
83 # * Remove the real file opening in the abstract VectorWriter
84 # * View frustum clipping
85 # * Scene clipping done using bounding box instead of object center
86 # * Fix camera type selection for blender>2.43 (Thanks to Thomas Lachmann)
87 # * Compatibility with python 2.3
89 # ---------------------------------------------------------------------
92 from Blender import Scene, Object, Mesh, NMesh, Material, Lamp, Camera, Window
93 from Blender.Mathutils import *
99 return [tmpdict.setdefault(e,e) for e in alist if e not in tmpdict]
100 # in python > 2.4 we ca use the following
101 #return [ u for u in alist if u not in locals()['_[1]'] ]
107 # We use a global progress Indicator Object
111 # Some global settings
115 polygons['SHOW'] = True
116 polygons['SHADING'] = 'FLAT' # FLAT or TOON
117 polygons['HSR'] = 'PAINTER' # PAINTER or NEWELL
118 # Hidden to the user for now
119 polygons['EXPANSION_TRICK'] = True
121 polygons['TOON_LEVELS'] = 2
124 edges['SHOW'] = False
125 edges['SHOW_HIDDEN'] = False
126 edges['STYLE'] = 'MESH' # MESH or SILHOUETTE
128 edges['COLOR'] = [0, 0, 0]
131 output['FORMAT'] = 'SVG'
132 output['ANIMATION'] = False
133 output['JOIN_OBJECTS'] = True
139 def dumpfaces(flist, filename):
140 """Dump a single face to a file.
151 writerobj = SVGVectorWriter(filename)
154 writerobj._printPolygons(m)
160 sys.stderr.write(msg)
163 return (abs(v1[0]-v2[0]) < EPS and
164 abs(v1[1]-v2[1]) < EPS )
165 by_furthest_z = (lambda f1, f2:
166 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
181 # ---------------------------------------------------------------------
185 # ---------------------------------------------------------------------
191 """A utility class for HSR processing.
194 def is_nonplanar_quad(face):
195 """Determine if a quad is non-planar.
197 From: http://mathworld.wolfram.com/Coplanar.html
199 Geometric objects lying in a common plane are said to be coplanar.
200 Three noncollinear points determine a plane and so are trivially coplanar.
201 Four points are coplanar iff the volume of the tetrahedron defined by them is
207 | x_4 y_4 z_4 1 | == 0
209 Coplanarity is equivalent to the statement that the pair of lines
210 determined by the four points are not skew, and can be equivalently stated
211 in vector form as (x_3-x_1).[(x_2-x_1)x(x_4-x_3)]==0.
213 An arbitrary number of n points x_1, ..., x_n can be tested for
214 coplanarity by finding the point-plane distances of the points
215 x_4, ..., x_n from the plane determined by (x_1,x_2,x_3)
216 and checking if they are all zero.
217 If so, the points are all coplanar.
219 We here check only for 4-point complanarity.
225 print "ERROR a mesh in Blender can't have more than 4 vertices or less than 3"
229 # three points must be complanar
232 x1 = Vector(face[0].co)
233 x2 = Vector(face[1].co)
234 x3 = Vector(face[2].co)
235 x4 = Vector(face[3].co)
237 v = (x3-x1) * CrossVecs((x2-x1), (x4-x3))
243 is_nonplanar_quad = staticmethod(is_nonplanar_quad)
245 def pointInPolygon(poly, v):
248 pointInPolygon = staticmethod(pointInPolygon)
250 def edgeIntersection(s1, s2, do_perturbate=False):
252 (x1, y1) = s1[0].co[0], s1[0].co[1]
253 (x2, y2) = s1[1].co[0], s1[1].co[1]
255 (x3, y3) = s2[0].co[0], s2[0].co[1]
256 (x4, y4) = s2[1].co[0], s2[1].co[1]
264 # calculate delta values (vector components)
273 C = dy2 * dx1 - dx2 * dy1 # /* cross product */
274 if C == 0: #/* parallel */
277 dx3 = x1 - x3 # /* combined origin offset vector */
280 a1 = (dy3 * dx2 - dx3 * dy2) / C;
281 a2 = (dy3 * dx1 - dx3 * dy1) / C;
283 # check for degeneracies
285 #print_debug(str(a1)+"\n")
286 #print_debug(str(a2)+"\n\n")
288 if (a1 == 0 or a1 == 1 or a2 == 0 or a2 == 1):
289 # Intersection on boundaries, we consider the point external?
292 elif (a1>0.0 and a1<1.0 and a2>0.0 and a2<1.0): # /* lines cross */
298 return (NMesh.Vert(x, y, z), a1, a2)
301 # lines have intersections but not those segments
304 edgeIntersection = staticmethod(edgeIntersection)
306 def isVertInside(self, v):
310 # Create point at infinity
311 point_at_infinity = NMesh.Vert(-INF, v.co[1], -INF)
313 for i in range(len(self.v)):
314 s1 = (point_at_infinity, v)
315 s2 = (self.v[i-1], self.v[i])
317 if EQ(v.co, s2[0].co) or EQ(v.co, s2[1].co):
320 if HSR.edgeIntersection(s1, s2, do_perturbate=False):
324 if winding_number % 2 == 0 :
331 isVertInside = staticmethod(isVertInside)
335 return ((b[0] - a[0]) * (c[1] - a[1]) -
336 (b[1] - a[1]) * (c[0] - a[0]) )
338 det = staticmethod(det)
340 def pointInPolygon(q, P):
343 point_at_infinity = NMesh.Vert(-INF, q.co[1], -INF)
347 for i in range(len(P.v)):
350 if (det(q.co, point_at_infinity.co, p0.co)<0) != (det(q.co, point_at_infinity.co, p1.co)<0):
351 if det(p0.co, p1.co, q.co) == 0 :
354 elif (det(p0.co, p1.co, q.co)<0) != (det(p0.co, p1.co, point_at_infinity.co)<0):
359 pointInPolygon = staticmethod(pointInPolygon)
361 def projectionsOverlap(f1, f2):
362 """ If you have nonconvex, but still simple polygons, an acceptable method
363 is to iterate over all vertices and perform the Point-in-polygon test[1].
364 The advantage of this method is that you can compute the exact
365 intersection point and collision normal that you will need to simulate
366 collision. When you have the point that lies inside the other polygon, you
367 just iterate over all edges of the second polygon again and look for edge
368 intersections. Note that this method detects collsion when it already
369 happens. This algorithm is fast enough to perform it hundreds of times per
372 for i in range(len(f1.v)):
375 # If a point of f1 in inside f2, there is an overlap!
377 #if HSR.isVertInside(f2, v1):
378 if HSR.pointInPolygon(v1, f2):
381 # If not the polygon can be ovelap as well, so we check for
382 # intersection between an edge of f1 and all the edges of f2
386 for j in range(len(f2.v)):
393 intrs = HSR.edgeIntersection(e1, e2)
395 #print_debug(str(v0.co) + " " + str(v1.co) + " " +
396 # str(v2.co) + " " + str(v3.co) )
397 #print_debug("\nIntersection\n")
403 projectionsOverlap = staticmethod(projectionsOverlap)
405 def midpoint(p1, p2):
406 """Return the midpoint of two vertices.
408 m = MidpointVecs(Vector(p1), Vector(p2))
409 mv = NMesh.Vert(m[0], m[1], m[2])
413 midpoint = staticmethod(midpoint)
415 def facesplit(P, Q, facelist, nmesh):
416 """Split P or Q according to the strategy illustrated in the Newell's
420 by_furthest_z = (lambda f1, f2:
421 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
424 # Choose if split P on Q plane or vice-versa
428 d = HSR.Distance(Vector(Pi), Q)
431 pIntersectQ = (n != len(P))
435 d = HSR.Distance(Vector(Qi), P)
438 qIntersectP = (n != len(Q))
442 # 1. If parts of P lie in both half-spaces of Q
443 # then splice P in two with the plane of Q
449 newfaces = HSR.splitOn(plane, f)
451 # 2. Else if parts of Q lie in both half-space of P
452 # then splice Q in two with the plane of P
453 if qIntersectP and newfaces == None:
458 newfaces = HSR.splitOn(plane, f)
461 # 3. Else slice P in half through the mid-point of
462 # the longest pair of opposite sides
465 print "We ignore P..."
472 # v1 = midpoint(f[0], f[1])
473 # v2 = midpoint(f[1], f[2])
475 # v1 = midpoint(f[0], f[1])
476 # v2 = midpoint(f[2], f[3])
477 #vec3 = (Vector(v2)+10*Vector(f.normal))
479 #v3 = NMesh.Vert(vec3[0], vec3[1], vec3[2])
481 #plane = NMesh.Face([v1, v2, v3])
483 #newfaces = splitOn(plane, f)
487 print "Big FAT problem, we weren't able to split POLYGONS!"
493 # if v not in plane and v in nmesh.verts:
494 # nmesh.verts.remove(v)
499 nf.col = [f.col[0]] * len(nf.v)
504 nmesh.verts.append(v)
505 # insert pieces in the list
510 # and resort the faces
511 facelist.sort(by_furthest_z)
512 facelist.sort(lambda f1, f2: cmp(f1.smooth, f2.smooth))
515 #print [ f.smooth for f in facelist ]
519 facesplit = staticmethod(facesplit)
521 def isOnSegment(v1, v2, p, extremes_internal=False):
522 """Check if point p is in segment v1v2.
528 # Should we consider extreme points as internal ?
530 # if p == v1 or p == v2:
531 if l1 < EPS or l2 < EPS:
532 return extremes_internal
536 # if the sum of l1 and l2 is circa l, then the point is on segment,
537 if abs(l - (l1+l2)) < EPS:
542 isOnSegment = staticmethod(isOnSegment)
544 def Distance(point, face):
545 """ Calculate the distance between a point and a face.
547 An alternative but more expensive method can be:
549 ip = Intersect(Vector(face[0]), Vector(face[1]), Vector(face[2]),
550 Vector(face.no), Vector(point), 0)
552 d = Vector(ip - point).length
554 See: http://mathworld.wolfram.com/Point-PlaneDistance.html
558 plNormal = Vector(face.no)
559 plVert0 = Vector(face.v[0])
561 d = (plVert0 * plNormal) - (p * plNormal)
563 #d = plNormal * (plVert0 - p)
565 #print "\nd: %.10f - sel: %d, %s\n" % (d, face.sel, str(point))
569 Distance = staticmethod(Distance)
573 # make one or two new faces based on a list of vertex-indices
602 makeFaces = staticmethod(makeFaces)
604 def splitOn(Q, P, return_positive_faces=True, return_negative_faces=True):
605 """Split P using the plane of Q.
606 Logic taken from the knife.py python script
609 # Check if P and Q are parallel
610 u = CrossVecs(Vector(Q.no),Vector(P.no))
616 print "PARALLEL planes!!"
620 # The final aim is to find the intersection line between P
621 # and the plane of Q, and split P along this line
625 # Calculate point-plane Distance between vertices of P and plane Q
627 for i in range(0, nP):
628 d.append(HSR.Distance(P.v[i], Q))
641 #print "d0:", d0, "d1:", d1
643 # if the vertex lies in the cutplane
645 #print "d1 On cutplane"
646 posVertList.append(V1)
647 negVertList.append(V1)
649 # if the previous vertex lies in cutplane
651 #print "d0 on Cutplane"
653 #print "d1 on positive Halfspace"
654 posVertList.append(V1)
656 #print "d1 on negative Halfspace"
657 negVertList.append(V1)
659 # if they are on the same side of the plane
661 #print "On the same half-space"
663 #print "d1 on positive Halfspace"
664 posVertList.append(V1)
666 #print "d1 on negative Halfspace"
667 negVertList.append(V1)
669 # the vertices are not on the same side of the plane, so we have an intersection
671 #print "Intersection"
673 e = Vector(V0), Vector(V1)
674 tri = Vector(Q[0]), Vector(Q[1]), Vector(Q[2])
676 inters = Intersect(tri[0], tri[1], tri[2], e[1]-e[0], e[0], 0)
681 #print "Intersection", inters
683 nv = NMesh.Vert(inters[0], inters[1], inters[2])
684 newVertList.append(nv)
686 posVertList.append(nv)
687 negVertList.append(nv)
690 posVertList.append(V1)
692 negVertList.append(V1)
695 # uniq for python > 2.4
696 #posVertList = [ u for u in posVertList if u not in locals()['_[1]'] ]
697 #negVertList = [ u for u in negVertList if u not in locals()['_[1]'] ]
699 # a more portable way
700 posVertList = uniq(posVertList)
701 negVertList = uniq(negVertList)
704 # If vertex are all on the same half-space, return
705 #if len(posVertList) < 3:
706 # print "Problem, we created a face with less that 3 vertices??"
708 #if len(negVertList) < 3:
709 # print "Problem, we created a face with less that 3 vertices??"
712 if len(posVertList) < 3 or len(negVertList) < 3:
713 #print "RETURN NONE, SURE???"
716 if not return_positive_faces:
718 if not return_negative_faces:
721 newfaces = HSR.addNewFaces(posVertList, negVertList)
725 splitOn = staticmethod(splitOn)
727 def addNewFaces(posVertList, negVertList):
728 # Create new faces resulting from the split
730 if len(posVertList) or len(negVertList):
732 #newfaces = [posVertList] + [negVertList]
733 newfaces = ( [[ NMesh.Vert(v[0], v[1], v[2]) for v in posVertList]] +
734 [[ NMesh.Vert(v[0], v[1], v[2]) for v in negVertList]] )
738 outfaces += HSR.makeFaces(nf)
743 addNewFaces = staticmethod(addNewFaces)
746 # ---------------------------------------------------------------------
748 ## Mesh Utility class
750 # ---------------------------------------------------------------------
754 def buildEdgeFaceUsersCache(me):
756 Takes a mesh and returns a list aligned with the meshes edges.
757 Each item is a list of the faces that use the edge
758 would be the equiv for having ed.face_users as a property
760 Taken from .blender/scripts/bpymodules/BPyMesh.py,
761 thanks to ideasman_42.
764 def sorted_edge_indicies(ed):
772 face_edges_dict= dict([(sorted_edge_indicies(ed), (ed.index, [])) for ed in me.edges])
774 fvi= [v.index for v in f.v]# face vert idx's
775 for i in xrange(len(f)):
782 face_edges_dict[i1,i2][1].append(f)
784 face_edges= [None] * len(me.edges)
785 for ed_index, ed_faces in face_edges_dict.itervalues():
786 face_edges[ed_index]= ed_faces
790 def isMeshEdge(adjacent_faces):
793 A mesh edge is visible if _at_least_one_ of its adjacent faces is selected.
794 Note: if the edge has no adjacent faces we want to show it as well,
795 useful for "edge only" portion of objects.
798 if len(adjacent_faces) == 0:
801 selected_faces = [f for f in adjacent_faces if f.sel]
803 if len(selected_faces) != 0:
808 def isSilhouetteEdge(adjacent_faces):
809 """Silhuette selection rule.
811 An edge is a silhuette edge if it is shared by two faces with
812 different selection status or if it is a boundary edge of a selected
816 if ((len(adjacent_faces) == 1 and adjacent_faces[0].sel == 1) or
817 (len(adjacent_faces) == 2 and
818 adjacent_faces[0].sel != adjacent_faces[1].sel)
824 buildEdgeFaceUsersCache = staticmethod(buildEdgeFaceUsersCache)
825 isMeshEdge = staticmethod(isMeshEdge)
826 isSilhouetteEdge = staticmethod(isSilhouetteEdge)
829 # ---------------------------------------------------------------------
831 ## Shading Utility class
833 # ---------------------------------------------------------------------
839 def toonShadingMapSetup():
840 levels = config.polygons['TOON_LEVELS']
842 texels = 2*levels - 1
843 tmp_shademap = [0.0] + [(i)/float(texels-1) for i in xrange(1, texels-1) ] + [1.0]
849 shademap = ShadingUtils.shademap
852 shademap = ShadingUtils.toonShadingMapSetup()
855 for i in xrange(0, len(shademap)-1):
856 pivot = (shademap[i]+shademap[i+1])/2.0
861 if v < shademap[i+1]:
866 toonShadingMapSetup = staticmethod(toonShadingMapSetup)
867 toonShading = staticmethod(toonShading)
870 # ---------------------------------------------------------------------
872 ## Projections classes
874 # ---------------------------------------------------------------------
877 """Calculate the projection of an object given the camera.
879 A projector is useful to so some per-object transformation to obtain the
880 projection of an object given the camera.
882 The main method is #doProjection# see the method description for the
886 def __init__(self, cameraObj, canvasRatio):
887 """Calculate the projection matrix.
889 The projection matrix depends, in this case, on the camera settings.
890 TAKE CARE: This projector expects vertices in World Coordinates!
893 camera = cameraObj.getData()
895 aspect = float(canvasRatio[0])/float(canvasRatio[1])
896 near = camera.clipStart
899 scale = float(camera.scale)
901 fovy = atan(0.5/aspect/(camera.lens/32))
902 fovy = fovy * 360.0/pi
905 if Blender.Get('version') < 243:
912 # What projection do we want?
913 if camera.type == camPersp:
914 mP = self._calcPerspectiveMatrix(fovy, aspect, near, far)
915 elif camera.type == camOrtho:
916 mP = self._calcOrthoMatrix(fovy, aspect, near, far, scale)
919 # View transformation
920 cam = Matrix(cameraObj.getInverseMatrix())
925 self.projectionMatrix = mP
931 def doProjection(self, v):
932 """Project the point on the view plane.
934 Given a vertex calculate the projection using the current projection
938 # Note that we have to work on the vertex using homogeneous coordinates
939 # From blender 2.42+ we don't need to resize the vector to be 4d
940 # when applying a 4x4 matrix, but we do that anyway since we need the
941 # 4th coordinate later
942 p = self.projectionMatrix * Vector(v).resize4D()
944 # Perspective division
961 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
962 """Return a perspective projection matrix.
965 top = near * tan(fovy * pi / 360.0)
969 x = (2.0 * near) / (right-left)
970 y = (2.0 * near) / (top-bottom)
971 a = (right+left) / (right-left)
972 b = (top+bottom) / (top - bottom)
973 c = - ((far+near) / (far-near))
974 d = - ((2*far*near)/(far-near))
980 [0.0, 0.0, -1.0, 0.0])
984 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
985 """Return an orthogonal projection matrix.
988 # The 11 in the formula was found emiprically
989 top = near * tan(fovy * pi / 360.0) * (scale * 11)
991 left = bottom * aspect
996 tx = -((right+left)/rl)
997 ty = -((top+bottom)/tb)
1001 [2.0/rl, 0.0, 0.0, tx],
1002 [0.0, 2.0/tb, 0.0, ty],
1003 [0.0, 0.0, 2.0/fn, tz],
1004 [0.0, 0.0, 0.0, 1.0])
1009 # ---------------------------------------------------------------------
1011 ## Progress Indicator
1013 # ---------------------------------------------------------------------
1016 """A model for a progress indicator.
1018 Do the progress calculation calculation and
1019 the view independent stuff of a progress indicator.
1021 def __init__(self, steps=0):
1027 def setSteps(self, steps):
1028 """Set the number of steps of the activity wich we want to track.
1035 def setName(self, name):
1036 """Set the name of the activity wich we want to track.
1043 def getProgress(self):
1044 return self.progress
1051 """Update the model, call this method when one step is completed.
1053 if self.progress == 100:
1057 self.progress = ( float(self.completed) / float(self.steps) ) * 100
1058 self.progress = int(self.progress)
1063 class ProgressIndicator:
1064 """An abstraction of a View for the Progress Model
1068 # Use a refresh rate so we do not show the progress at
1069 # every update, but every 'self.refresh_rate' times.
1070 self.refresh_rate = 10
1071 self.shows_counter = 0
1075 self.progressModel = None
1077 def setQuiet(self, value):
1080 def setActivity(self, name, steps):
1081 """Initialize the Model.
1083 In a future version (with subactivities-progress support) this method
1084 could only set the current activity.
1086 self.progressModel = Progress()
1087 self.progressModel.setName(name)
1088 self.progressModel.setSteps(steps)
1090 def getActivity(self):
1091 return self.progressModel
1094 """Update the model and show the actual progress.
1096 assert(self.progressModel)
1098 if self.progressModel.update():
1102 self.show(self.progressModel.getProgress(),
1103 self.progressModel.getName())
1105 # We return always True here so we can call the update() method also
1106 # from lambda funcs (putting the call in logical AND with other ops)
1109 def show(self, progress, name=""):
1110 self.shows_counter = (self.shows_counter + 1) % self.refresh_rate
1111 if self.shows_counter != 0:
1115 self.shows_counter = -1
1118 class ConsoleProgressIndicator(ProgressIndicator):
1119 """Show a progress bar on stderr, a la wget.
1122 ProgressIndicator.__init__(self)
1124 self.swirl_chars = ["-", "\\", "|", "/"]
1125 self.swirl_count = -1
1127 def show(self, progress, name):
1128 ProgressIndicator.show(self, progress, name)
1131 bar_progress = int( (progress/100.0) * bar_length )
1132 bar = ("=" * bar_progress).ljust(bar_length)
1134 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1135 swirl_char = self.swirl_chars[self.swirl_count]
1137 progress_bar = "%s |%s| %c %3d%%" % (name, bar, swirl_char, progress)
1139 sys.stderr.write(progress_bar+"\r")
1141 sys.stderr.write("\n")
1144 class GraphicalProgressIndicator(ProgressIndicator):
1145 """Interface to the Blender.Window.DrawProgressBar() method.
1148 ProgressIndicator.__init__(self)
1150 #self.swirl_chars = ["-", "\\", "|", "/"]
1151 # We have to use letters with the same width, for now!
1152 # Blender progress bar considers the font widths when
1153 # calculating the progress bar width.
1154 self.swirl_chars = ["\\", "/"]
1155 self.swirl_count = -1
1157 def show(self, progress, name):
1158 ProgressIndicator.show(self, progress)
1160 self.swirl_count = (self.swirl_count+1)%len(self.swirl_chars)
1161 swirl_char = self.swirl_chars[self.swirl_count]
1163 progress_text = "%s - %c %3d%%" % (name, swirl_char, progress)
1165 # Finally draw the Progress Bar
1166 Window.WaitCursor(1) # Maybe we can move that call in the constructor?
1167 Window.DrawProgressBar(progress/100.0, progress_text)
1170 Window.DrawProgressBar(1, progress_text)
1171 Window.WaitCursor(0)
1175 # ---------------------------------------------------------------------
1177 ## 2D Object representation class
1179 # ---------------------------------------------------------------------
1181 # TODO: a class to represent the needed properties of a 2D vector image
1182 # For now just using a [N]Mesh structure.
1185 # ---------------------------------------------------------------------
1187 ## Vector Drawing Classes
1189 # ---------------------------------------------------------------------
1195 A class for printing output in a vectorial format.
1197 Given a 2D representation of the 3D scene the class is responsible to
1198 write it is a vector format.
1200 Every subclasses of VectorWriter must have at last the following public
1204 - printCanvas(self, scene,
1205 doPrintPolygons=True, doPrintEdges=False, showHiddenEdges=False):
1208 def __init__(self, fileName):
1209 """Set the output file name and other properties"""
1211 self.outputFileName = fileName
1213 context = Scene.GetCurrent().getRenderingContext()
1214 self.canvasSize = ( context.imageSizeX(), context.imageSizeY() )
1216 self.fps = context.fps
1220 self.animation = False
1227 def open(self, startFrame=1, endFrame=1):
1228 if startFrame != endFrame:
1229 self.startFrame = startFrame
1230 self.endFrame = endFrame
1231 self.animation = True
1233 print "Outputting to: ", self.outputFileName
1240 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1241 showHiddenEdges=False):
1242 """This is the interface for the needed printing routine.
1249 class SVGVectorWriter(VectorWriter):
1250 """A concrete class for writing SVG output.
1253 def __init__(self, fileName):
1254 """Simply call the parent Contructor.
1256 VectorWriter.__init__(self, fileName)
1265 def open(self, startFrame=1, endFrame=1):
1266 """Do some initialization operations.
1268 VectorWriter.open(self, startFrame, endFrame)
1270 self.file = open(self.outputFileName, "w")
1275 """Do some finalization operation.
1282 # remember to call the close method of the parent as last
1283 VectorWriter.close(self)
1286 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1287 showHiddenEdges=False):
1288 """Convert the scene representation to SVG.
1291 Objects = scene.getChildren()
1293 context = scene.getRenderingContext()
1294 framenumber = context.currentFrame()
1297 framestyle = "display:none"
1299 framestyle = "display:block"
1301 # Assign an id to this group so we can set properties on it using DOM
1302 self.file.write("<g id=\"frame%d\" style=\"%s\">\n" %
1303 (framenumber, framestyle) )
1308 if(obj.getType() != 'Mesh'):
1311 self.file.write("<g id=\"%s\">\n" % obj.getName())
1313 mesh = obj.getData(mesh=1)
1316 self._printPolygons(mesh)
1319 self._printEdges(mesh, showHiddenEdges)
1321 self.file.write("</g>\n")
1323 self.file.write("</g>\n")
1330 def _calcCanvasCoord(self, v):
1331 """Convert vertex in scene coordinates to canvas coordinates.
1334 pt = Vector([0, 0, 0])
1336 mW = float(self.canvasSize[0])/2.0
1337 mH = float(self.canvasSize[1])/2.0
1339 # rescale to canvas size
1340 pt[0] = v.co[0]*mW + mW
1341 pt[1] = v.co[1]*mH + mH
1344 # For now we want (0,0) in the top-left corner of the canvas.
1345 # Mirror and translate along y
1347 pt[1] += self.canvasSize[1]
1351 def _printHeader(self):
1352 """Print SVG header."""
1354 self.file.write("<?xml version=\"1.0\"?>\n")
1355 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n")
1356 self.file.write("\t\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
1357 self.file.write("<svg version=\"1.0\"\n")
1358 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
1359 self.file.write("\twidth=\"%d\" height=\"%d\">\n\n" %
1363 delay = 1000/self.fps
1365 self.file.write("""\n<script type="text/javascript"><![CDATA[
1366 globalStartFrame=%d;
1369 timerID = setInterval("NextFrame()", %d);
1370 globalFrameCounter=%d;
1371 \n""" % (self.startFrame, self.endFrame, delay, self.startFrame) )
1373 self.file.write("""\n
1374 function NextFrame()
1376 currentElement = document.getElementById('frame'+globalFrameCounter)
1377 previousElement = document.getElementById('frame'+(globalFrameCounter-1))
1379 if (!currentElement)
1384 if (globalFrameCounter > globalEndFrame)
1386 clearInterval(timerID)
1392 previousElement.style.display="none";
1394 currentElement.style.display="block";
1395 globalFrameCounter++;
1401 def _printFooter(self):
1402 """Print the SVG footer."""
1404 self.file.write("\n</svg>\n")
1406 def _printPolygons(self, mesh):
1407 """Print the selected (visible) polygons.
1410 if len(mesh.faces) == 0:
1413 self.file.write("<g>\n")
1415 for face in mesh.faces:
1419 self.file.write("<path d=\"")
1421 #p = self._calcCanvasCoord(face.verts[0])
1422 p = self._calcCanvasCoord(face.v[0])
1423 self.file.write("M %g,%g L " % (p[0], p[1]))
1425 for v in face.v[1:]:
1426 p = self._calcCanvasCoord(v)
1427 self.file.write("%g,%g " % (p[0], p[1]))
1429 # get rid of the last blank space, just cosmetics here.
1430 self.file.seek(-1, 1)
1431 self.file.write(" z\"\n")
1433 # take as face color the first vertex color
1436 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1438 color = [255, 255, 255, 255]
1440 # Convert the color to the #RRGGBB form
1441 str_col = "#%02X%02X%02X" % (color[0], color[1], color[2])
1443 # Handle transparent polygons
1446 opacity = float(color[3])/255.0
1447 opacity_string = " fill-opacity: %g; stroke-opacity: %g; opacity: 1;" % (opacity, opacity)
1448 #opacity_string = "opacity: %g;" % (opacity)
1450 self.file.write("\tstyle=\"fill:" + str_col + ";")
1451 self.file.write(opacity_string)
1453 # use the stroke property to alleviate the "adjacent edges" problem,
1454 # we simulate polygon expansion using borders,
1455 # see http://www.antigrain.com/svg/index.html for more info
1458 # EXPANSION TRICK is not that useful where there is transparency
1459 if config.polygons['EXPANSION_TRICK'] and color[3] == 255:
1460 # str_col = "#000000" # For debug
1461 self.file.write(" stroke:%s;\n" % str_col)
1462 self.file.write(" stroke-width:" + str(stroke_width) + ";\n")
1463 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1465 self.file.write("\"/>\n")
1467 self.file.write("</g>\n")
1469 def _printEdges(self, mesh, showHiddenEdges=False):
1470 """Print the wireframe using mesh edges.
1473 stroke_width = config.edges['WIDTH']
1474 stroke_col = config.edges['COLOR']
1476 self.file.write("<g>\n")
1478 for e in mesh.edges:
1480 hidden_stroke_style = ""
1483 if showHiddenEdges == False:
1486 hidden_stroke_style = ";\n stroke-dasharray:3, 3"
1488 p1 = self._calcCanvasCoord(e.v1)
1489 p2 = self._calcCanvasCoord(e.v2)
1491 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
1492 % ( p1[0], p1[1], p2[0], p2[1] ) )
1493 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
1494 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
1495 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
1496 self.file.write(hidden_stroke_style)
1497 self.file.write("\"/>\n")
1499 self.file.write("</g>\n")
1508 SWFSupported = False
1510 class SWFVectorWriter(VectorWriter):
1511 """A concrete class for writing SWF output.
1514 def __init__(self, fileName):
1515 """Simply call the parent Contructor.
1517 VectorWriter.__init__(self, fileName)
1527 def open(self, startFrame=1, endFrame=1):
1528 """Do some initialization operations.
1530 VectorWriter.open(self, startFrame, endFrame)
1531 self.movie = SWFMovie()
1532 self.movie.setDimension(self.canvasSize[0], self.canvasSize[1])
1534 self.movie.setRate(self.fps)
1535 numframes = endFrame - startFrame + 1
1536 self.movie.setFrames(numframes)
1539 """Do some finalization operation.
1541 self.movie.save(self.outputFileName)
1543 # remember to call the close method of the parent
1544 VectorWriter.close(self)
1546 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1547 showHiddenEdges=False):
1548 """Convert the scene representation to SVG.
1550 context = scene.getRenderingContext()
1551 framenumber = context.currentFrame()
1553 Objects = scene.getChildren()
1556 self.movie.remove(self.sprite)
1558 sprite = SWFSprite()
1562 if(obj.getType() != 'Mesh'):
1565 mesh = obj.getData(mesh=1)
1568 self._printPolygons(mesh, sprite)
1571 self._printEdges(mesh, sprite, showHiddenEdges)
1574 i = self.movie.add(sprite)
1575 # Remove the instance the next time
1578 self.movie.nextFrame()
1585 def _calcCanvasCoord(self, v):
1586 """Convert vertex in scene coordinates to canvas coordinates.
1589 pt = Vector([0, 0, 0])
1591 mW = float(self.canvasSize[0])/2.0
1592 mH = float(self.canvasSize[1])/2.0
1594 # rescale to canvas size
1595 pt[0] = v.co[0]*mW + mW
1596 pt[1] = v.co[1]*mH + mH
1599 # For now we want (0,0) in the top-left corner of the canvas.
1600 # Mirror and translate along y
1602 pt[1] += self.canvasSize[1]
1606 def _printPolygons(self, mesh, sprite):
1607 """Print the selected (visible) polygons.
1610 if len(mesh.faces) == 0:
1613 for face in mesh.faces:
1619 color = [fcol.r, fcol.g, fcol.b, fcol.a]
1621 color = [255, 255, 255, 255]
1624 f = s.addFill(color[0], color[1], color[2], color[3])
1627 # The starting point of the shape
1628 p0 = self._calcCanvasCoord(face.verts[0])
1629 s.movePenTo(p0[0], p0[1])
1631 for v in face.verts[1:]:
1632 p = self._calcCanvasCoord(v)
1633 s.drawLineTo(p[0], p[1])
1636 s.drawLineTo(p0[0], p0[1])
1642 def _printEdges(self, mesh, sprite, showHiddenEdges=False):
1643 """Print the wireframe using mesh edges.
1646 stroke_width = config.edges['WIDTH']
1647 stroke_col = config.edges['COLOR']
1651 for e in mesh.edges:
1653 # Next, we set the line width and color for our shape.
1654 s.setLine(stroke_width, stroke_col[0], stroke_col[1], stroke_col[2],
1658 if showHiddenEdges == False:
1661 # SWF does not support dashed lines natively, so -for now-
1662 # draw hidden lines thinner and half-trasparent
1663 s.setLine(stroke_width/2, stroke_col[0], stroke_col[1],
1666 p1 = self._calcCanvasCoord(e.v1)
1667 p2 = self._calcCanvasCoord(e.v2)
1669 s.movePenTo(p1[0], p1[1])
1670 s.drawLineTo(p2[0], p2[1])
1679 from reportlab.pdfgen import canvas
1682 PDFSupported = False
1684 class PDFVectorWriter(VectorWriter):
1685 """A concrete class for writing PDF output.
1688 def __init__(self, fileName):
1689 """Simply call the parent Contructor.
1691 VectorWriter.__init__(self, fileName)
1700 def open(self, startFrame=1, endFrame=1):
1701 """Do some initialization operations.
1703 VectorWriter.open(self, startFrame, endFrame)
1704 size = (self.canvasSize[0], self.canvasSize[1])
1705 self.canvas = canvas.Canvas(self.outputFileName, pagesize=size, bottomup=0)
1708 """Do some finalization operation.
1712 # remember to call the close method of the parent
1713 VectorWriter.close(self)
1715 def printCanvas(self, scene, doPrintPolygons=True, doPrintEdges=False,
1716 showHiddenEdges=False):
1717 """Convert the scene representation to SVG.
1719 context = scene.getRenderingContext()
1720 framenumber = context.currentFrame()
1722 Objects = scene.getChildren()
1726 if(obj.getType() != 'Mesh'):
1729 mesh = obj.getData(mesh=1)
1732 self._printPolygons(mesh)
1735 self._printEdges(mesh, showHiddenEdges)
1737 self.canvas.showPage()
1743 def _calcCanvasCoord(self, v):
1744 """Convert vertex in scene coordinates to canvas coordinates.
1747 pt = Vector([0, 0, 0])
1749 mW = float(self.canvasSize[0])/2.0
1750 mH = float(self.canvasSize[1])/2.0
1752 # rescale to canvas size
1753 pt[0] = v.co[0]*mW + mW
1754 pt[1] = v.co[1]*mH + mH
1757 # For now we want (0,0) in the top-left corner of the canvas.
1758 # Mirror and translate along y
1760 pt[1] += self.canvasSize[1]
1764 def _printPolygons(self, mesh):
1765 """Print the selected (visible) polygons.
1768 if len(mesh.faces) == 0:
1771 for face in mesh.faces:
1777 color = [fcol.r/255.0, fcol.g/255.0, fcol.b/255.0,
1780 color = [1, 1, 1, 1]
1782 self.canvas.setFillColorRGB(color[0], color[1], color[2])
1784 self.canvas.setStrokeColorRGB(0, 0, 0)
1786 path = self.canvas.beginPath()
1788 # The starting point of the path
1789 p0 = self._calcCanvasCoord(face.verts[0])
1790 path.moveTo(p0[0], p0[1])
1792 for v in face.verts[1:]:
1793 p = self._calcCanvasCoord(v)
1794 path.lineTo(p[0], p[1])
1799 self.canvas.drawPath(path, stroke=0, fill=1)
1801 def _printEdges(self, mesh, showHiddenEdges=False):
1802 """Print the wireframe using mesh edges.
1805 stroke_width = config.edges['WIDTH']
1806 stroke_col = config.edges['COLOR']
1808 self.canvas.setLineCap(1)
1809 self.canvas.setLineJoin(1)
1810 self.canvas.setLineWidth(stroke_width)
1811 self.canvas.setStrokeColorRGB(stroke_col[0]/255.0, stroke_col[1]/255.0,
1814 for e in mesh.edges:
1816 self.canvas.setLineWidth(stroke_width)
1819 if showHiddenEdges == False:
1822 # PDF does not support dashed lines natively, so -for now-
1823 # draw hidden lines thinner
1824 self.canvas.setLineWidth(stroke_width/2.0)
1826 p1 = self._calcCanvasCoord(e.v1)
1827 p2 = self._calcCanvasCoord(e.v2)
1829 self.canvas.line(p1[0], p1[1], p2[0], p2[1])
1833 # ---------------------------------------------------------------------
1835 ## Rendering Classes
1837 # ---------------------------------------------------------------------
1839 # A dictionary to collect different shading style methods
1840 shadingStyles = dict()
1841 shadingStyles['FLAT'] = None
1842 shadingStyles['TOON'] = None
1844 # A dictionary to collect different edge style methods
1846 edgeStyles['MESH'] = MeshUtils.isMeshEdge
1847 edgeStyles['SILHOUETTE'] = MeshUtils.isSilhouetteEdge
1849 # A dictionary to collect the supported output formats
1850 outputWriters = dict()
1851 outputWriters['SVG'] = SVGVectorWriter
1853 outputWriters['SWF'] = SWFVectorWriter
1855 outputWriters['PDF'] = PDFVectorWriter
1859 """Render a scene viewed from the active camera.
1861 This class is responsible of the rendering process, transformation and
1862 projection of the objects in the scene are invoked by the renderer.
1864 The rendering is done using the active camera for the current scene.
1868 """Make the rendering process only for the current scene by default.
1870 We will work on a copy of the scene, to be sure that the current scene do
1871 not get modified in any way.
1874 # Render the current Scene, this should be a READ-ONLY property
1875 self._SCENE = Scene.GetCurrent()
1877 # Use the aspect ratio of the scene rendering context
1878 context = self._SCENE.getRenderingContext()
1880 aspect_ratio = float(context.imageSizeX())/float(context.imageSizeY())
1881 self.canvasRatio = (float(context.aspectRatioX())*aspect_ratio,
1882 float(context.aspectRatioY())
1885 # Render from the currently active camera
1886 #self.cameraObj = self._SCENE.getCurrentCamera()
1888 # Get the list of lighting sources
1889 obj_lst = self._SCENE.getChildren()
1890 self.lights = [ o for o in obj_lst if o.getType() == 'Lamp']
1892 # When there are no lights we use a default lighting source
1893 # that have the same position of the camera
1894 if len(self.lights) == 0:
1895 l = Lamp.New('Lamp')
1896 lobj = Object.New('Lamp')
1897 lobj.loc = self.cameraObj.loc
1899 self.lights.append(lobj)
1906 def doRendering(self, outputWriter, animation=False):
1907 """Render picture or animation and write it out.
1910 - a Vector writer object that will be used to output the result.
1911 - a flag to tell if we want to render an animation or only the
1915 context = self._SCENE.getRenderingContext()
1916 origCurrentFrame = context.currentFrame()
1918 # Handle the animation case
1920 startFrame = origCurrentFrame
1921 endFrame = startFrame
1924 startFrame = context.startFrame()
1925 endFrame = context.endFrame()
1926 outputWriter.open(startFrame, endFrame)
1928 # Do the rendering process frame by frame
1929 print "Start Rendering of %d frames" % (endFrame-startFrame+1)
1930 for f in xrange(startFrame, endFrame+1):
1931 print "\n\nFrame: %d" % f
1933 # FIXME To get the correct camera position we have to use +1 here.
1934 # Is there a bug somewhere in the Scene module?
1935 context.currentFrame(f+1)
1936 self.cameraObj = self._SCENE.getCurrentCamera()
1938 # Use some temporary workspace, a full copy of the scene
1939 inputScene = self._SCENE.copy(2)
1941 # To get the objects at this frame remove the +1 ...
1942 ctx = inputScene.getRenderingContext()
1946 # Get a projector for this camera.
1947 # NOTE: the projector wants object in world coordinates,
1948 # so we should remember to apply modelview transformations
1949 # _before_ we do projection transformations.
1950 self.proj = Projector(self.cameraObj, self.canvasRatio)
1953 renderedScene = self.doRenderScene(inputScene)
1955 print "There was an error! Aborting."
1957 print traceback.print_exc()
1959 self._SCENE.makeCurrent()
1960 Scene.unlink(inputScene)
1964 outputWriter.printCanvas(renderedScene,
1965 doPrintPolygons = config.polygons['SHOW'],
1966 doPrintEdges = config.edges['SHOW'],
1967 showHiddenEdges = config.edges['SHOW_HIDDEN'])
1969 # delete the rendered scene
1970 self._SCENE.makeCurrent()
1971 Scene.unlink(renderedScene)
1974 outputWriter.close()
1976 context.currentFrame(origCurrentFrame)
1979 def doRenderScene(self, workScene):
1980 """Control the rendering process.
1982 Here we control the entire rendering process invoking the operation
1983 needed to transform and project the 3D scene in two dimensions.
1986 # global processing of the scene
1988 self._doSceneClipping(workScene)
1990 self._doConvertGeometricObjsToMesh(workScene)
1992 if config.output['JOIN_OBJECTS']:
1993 self._joinMeshObjectsInScene(workScene)
1995 self._doSceneDepthSorting(workScene)
1997 # Per object activities
1999 Objects = workScene.getChildren()
2000 print "Total Objects: %d" % len(Objects)
2001 for i,obj in enumerate(Objects):
2003 print "Rendering Object: %d" % i
2005 if obj.getType() != 'Mesh':
2006 print "Only Mesh supported! - Skipping type:", obj.getType()
2009 print "Rendering: ", obj.getName()
2011 mesh = obj.getData(mesh=1)
2013 self._doModelingTransformation(mesh, obj.matrix)
2015 self._doBackFaceCulling(mesh)
2018 # When doing HSR with NEWELL we may want to flip all normals
2020 if config.polygons['HSR'] == "NEWELL":
2021 for f in mesh.faces:
2024 for f in mesh.faces:
2027 self._doLighting(mesh)
2029 # Do "projection" now so we perform further processing
2030 # in Normalized View Coordinates
2031 self._doProjection(mesh, self.proj)
2033 self._doViewFrustumClipping(mesh)
2035 self._doHiddenSurfaceRemoval(mesh)
2037 self._doEdgesStyle(mesh, edgeStyles[config.edges['STYLE']])
2039 # Update the object data, important! :)
2051 def _getObjPosition(self, obj):
2052 """Return the obj position in World coordinates.
2054 return obj.matrix.translationPart()
2056 def _cameraViewVector(self):
2057 """Get the View Direction form the camera matrix.
2059 return Vector(self.cameraObj.matrix[2]).resize3D()
2064 def _isFaceVisible(self, face):
2065 """Determine if a face of an object is visible from the current camera.
2067 The view vector is calculated from the camera location and one of the
2068 vertices of the face (expressed in World coordinates, after applying
2069 modelview transformations).
2071 After those transformations we determine if a face is visible by
2072 computing the angle between the face normal and the view vector, this
2073 angle has to be between -90 and 90 degrees for the face to be visible.
2074 This corresponds somehow to the dot product between the two, if it
2075 results > 0 then the face is visible.
2077 There is no need to normalize those vectors since we are only interested in
2078 the sign of the cross product and not in the product value.
2080 NOTE: here we assume the face vertices are in WorldCoordinates, so
2081 please transform the object _before_ doing the test.
2084 normal = Vector(face.no)
2085 camPos = self._getObjPosition(self.cameraObj)
2088 # View Vector in orthographics projections is the view Direction of
2090 if self.cameraObj.data.getType() == 1:
2091 view_vect = self._cameraViewVector()
2093 # View vector in perspective projections can be considered as
2094 # the difference between the camera position and one point of
2095 # the face, we choose the farthest point from the camera.
2096 if self.cameraObj.data.getType() == 0:
2097 vv = max( [ ((camPos - Vector(v.co)).length, (camPos - Vector(v.co))) for v in face] )
2101 # if d > 0 the face is visible from the camera
2102 d = view_vect * normal
2112 def _doSceneClipping(self, scene):
2113 """Clip whole objects against the View Frustum.
2115 For now clip away only objects according to their center position.
2118 cam_pos = self._getObjPosition(self.cameraObj)
2119 view_vect = self._cameraViewVector()
2121 near = self.cameraObj.data.clipStart
2122 far = self.cameraObj.data.clipEnd
2124 aspect = float(self.canvasRatio[0])/float(self.canvasRatio[1])
2125 fovy = atan(0.5/aspect/(self.cameraObj.data.lens/32))
2126 fovy = fovy * 360.0/pi
2128 Objects = scene.getChildren()
2130 if o.getType() != 'Mesh': continue;
2133 obj_vect = Vector(cam_pos) - self._getObjPosition(o)
2135 d = obj_vect*view_vect
2136 theta = AngleBetweenVecs(obj_vect, view_vect)
2138 # if the object is outside the view frustum, clip it away
2139 if (d < near) or (d > far) or (theta > fovy):
2143 # Use the object bounding box
2144 # (whose points are already in WorldSpace Coordinate)
2146 bb = o.getBoundBox()
2150 p_vect = Vector(cam_pos) - Vector(p)
2152 d = p_vect * view_vect
2153 theta = AngleBetweenVecs(p_vect, view_vect)
2155 # Is this point outside the view frustum?
2156 if (d < near) or (d > far) or (theta > fovy):
2159 # If the bb is all outside the view frustum we clip the whole
2161 if points_outside == len(bb):
2166 def _doConvertGeometricObjsToMesh(self, scene):
2167 """Convert all "geometric" objects to mesh ones.
2169 geometricObjTypes = ['Mesh', 'Surf', 'Curve', 'Text']
2170 #geometricObjTypes = ['Mesh', 'Surf', 'Curve']
2172 Objects = scene.getChildren()
2173 objList = [ o for o in Objects if o.getType() in geometricObjTypes ]
2176 obj = self._convertToRawMeshObj(obj)
2178 scene.unlink(old_obj)
2181 # XXX Workaround for Text and Curve which have some normals
2182 # inverted when they are converted to Mesh, REMOVE that when
2183 # blender will fix that!!
2184 if old_obj.getType() in ['Curve', 'Text']:
2185 me = obj.getData(mesh=1)
2186 for f in me.faces: f.sel = 1;
2187 for v in me.verts: v.sel = 1;
2194 def _doSceneDepthSorting(self, scene):
2195 """Sort objects in the scene.
2197 The object sorting is done accordingly to the object centers.
2200 c = self._getObjPosition(self.cameraObj)
2202 by_obj_center_pos = (lambda o1, o2:
2203 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2204 cmp((self._getObjPosition(o1) - Vector(c)).length,
2205 (self._getObjPosition(o2) - Vector(c)).length)
2208 # Implement sorting by bounding box, the object with the bb
2209 # nearest to the camera should be drawn as last.
2210 by_nearest_bbox_point = (lambda o1, o2:
2211 (o1.getType() == 'Mesh' and o2.getType() == 'Mesh') and
2212 cmp( min( [(Vector(p) - Vector(c)).length for p in o1.getBoundBox()] ),
2213 min( [(Vector(p) - Vector(c)).length for p in o2.getBoundBox()] )
2218 Objects = scene.getChildren()
2219 #Objects.sort(by_obj_center_pos)
2220 Objects.sort(by_nearest_bbox_point)
2227 def _joinMeshObjectsInScene(self, scene):
2228 """Merge all the Mesh Objects in a scene into a single Mesh Object.
2231 oList = [o for o in scene.getChildren() if o.getType()=='Mesh']
2233 # FIXME: Object.join() do not work if the list contains 1 object
2237 mesh = Mesh.New('BigOne')
2238 bigObj = Object.New('Mesh', 'BigOne')
2245 except RuntimeError:
2246 print "\nWarning! - Can't Join Objects\n"
2247 scene.unlink(bigObj)
2250 print "Objects Type error?"
2258 # Per object/mesh methods
2260 def _convertToRawMeshObj(self, object):
2261 """Convert geometry based object to a mesh object.
2263 me = Mesh.New('RawMesh_'+object.name)
2264 me.getFromObject(object.name)
2266 newObject = Object.New('Mesh', 'RawMesh_'+object.name)
2269 # If the object has no materials set a default material
2270 if not me.materials:
2271 me.materials = [Material.New()]
2272 #for f in me.faces: f.mat = 0
2274 newObject.setMatrix(object.getMatrix())
2278 def _doModelingTransformation(self, mesh, matrix):
2279 """Transform object coordinates to world coordinates.
2281 This step is done simply applying to the object its tranformation
2282 matrix and recalculating its normals.
2284 # XXX FIXME: blender do not transform normals in the right way when
2285 # there are negative scale values
2286 if matrix[0][0] < 0 or matrix[1][1] < 0 or matrix[2][2] < 0:
2287 print "WARNING: Negative scales, expect incorrect results!"
2289 mesh.transform(matrix, True)
2291 def _doBackFaceCulling(self, mesh):
2292 """Simple Backface Culling routine.
2294 At this level we simply do a visibility test face by face and then
2295 select the vertices belonging to visible faces.
2298 # Select all vertices, so edges can be displayed even if there are no
2300 for v in mesh.verts:
2303 Mesh.Mode(Mesh.SelectModes['FACE'])
2305 for f in mesh.faces:
2307 if self._isFaceVisible(f):
2310 def _doLighting(self, mesh):
2311 """Apply an Illumination and shading model to the object.
2313 The model used is the Phong one, it may be inefficient,
2314 but I'm just learning about rendering and starting from Phong seemed
2315 the most natural way.
2318 # If the mesh has vertex colors already, use them,
2319 # otherwise turn them on and do some calculations
2320 if mesh.vertexColors:
2322 mesh.vertexColors = 1
2324 materials = mesh.materials
2326 camPos = self._getObjPosition(self.cameraObj)
2328 # We do per-face color calculation (FLAT Shading), we can easily turn
2329 # to a per-vertex calculation if we want to implement some shading
2330 # technique. For an example see:
2331 # http://www.miralab.unige.ch/papers/368.pdf
2332 for f in mesh.faces:
2338 mat = materials[f.mat]
2340 # A new default material
2342 mat = Material.New('defMat')
2344 # Check if it is a shadeless material
2345 elif mat.getMode() & Material.Modes['SHADELESS']:
2347 # Convert to a value between 0 and 255
2348 tmp_col = [ int(c * 255.0) for c in I]
2359 # do vertex color calculation
2361 TotDiffSpec = Vector([0.0, 0.0, 0.0])
2363 for l in self.lights:
2365 light_pos = self._getObjPosition(l)
2366 light = light_obj.getData()
2368 L = Vector(light_pos).normalize()
2370 V = (Vector(camPos) - Vector(f.cent)).normalize()
2372 N = Vector(f.no).normalize()
2374 if config.polygons['SHADING'] == 'TOON':
2375 NL = ShadingUtils.toonShading(N*L)
2379 # Should we use NL instead of (N*L) here?
2380 R = 2 * (N*L) * N - L
2382 Ip = light.getEnergy()
2384 # Diffuse co-efficient
2385 kd = mat.getRef() * Vector(mat.getRGBCol())
2387 kd[i] *= light.col[i]
2389 Idiff = Ip * kd * max(0, NL)
2392 # Specular component
2393 ks = mat.getSpec() * Vector(mat.getSpecCol())
2394 ns = mat.getHardness()
2395 Ispec = Ip * ks * pow(max(0, (V*R)), ns)
2397 TotDiffSpec += (Idiff+Ispec)
2401 Iamb = Vector(Blender.World.Get()[0].getAmb())
2404 # Emissive component (convert to a triplet)
2405 ki = Vector([mat.getEmit()]*3)
2407 #I = ki + Iamb + (Idiff + Ispec)
2408 I = ki + (ka * Iamb) + TotDiffSpec
2411 # Set Alpha component
2413 I.append(mat.getAlpha())
2415 # Clamp I values between 0 and 1
2416 I = [ min(c, 1) for c in I]
2417 I = [ max(0, c) for c in I]
2419 # Convert to a value between 0 and 255
2420 tmp_col = [ int(c * 255.0) for c in I]
2428 def _doProjection(self, mesh, projector):
2429 """Apply Viewing and Projection tranformations.
2432 for v in mesh.verts:
2433 p = projector.doProjection(v.co[:])
2438 #mesh.recalcNormals()
2441 # We could reeset Camera matrix, since now
2442 # we are in Normalized Viewing Coordinates,
2443 # but doung that would affect World Coordinate
2444 # processing for other objects
2446 #self.cameraObj.data.type = 1
2447 #self.cameraObj.data.scale = 2.0
2448 #m = Matrix().identity()
2449 #self.cameraObj.setMatrix(m)
2451 def _doViewFrustumClipping(self, mesh):
2452 """Clip faces against the View Frustum.
2455 # The Canonical View Volume, 8 vertices, and 6 faces,
2456 # We consider its face normals pointing outside
2458 v1 = NMesh.Vert(1, 1, -1)
2459 v2 = NMesh.Vert(1, -1, -1)
2460 v3 = NMesh.Vert(-1, -1, -1)
2461 v4 = NMesh.Vert(-1, 1, -1)
2462 v5 = NMesh.Vert(1, 1, 1)
2463 v6 = NMesh.Vert(1, -1, 1)
2464 v7 = NMesh.Vert(-1, -1, 1)
2465 v8 = NMesh.Vert(-1, 1, 1)
2468 f1 = NMesh.Face([v1, v4, v3, v2])
2470 f2 = NMesh.Face([v5, v6, v7, v8])
2472 f3 = NMesh.Face([v1, v2, v6, v5])
2474 f4 = NMesh.Face([v2, v3, v7, v6])
2476 f5 = NMesh.Face([v3, v4, v8, v7])
2478 f6 = NMesh.Face([v4, v1, v5, v8])
2481 nmesh = NMesh.GetRaw(mesh.name)
2482 clippedfaces = nmesh.faces[:]
2483 facelist = clippedfaces[:]
2485 for clipface in cvv:
2491 newfaces = HSR.splitOn(clipface, f, return_positive_faces=False)
2494 # Check if the face is all outside the view frustum
2495 # TODO: Do this test before, it is more efficient
2498 if abs(v[0]) > 1-EPS or abs(v[1]) > 1-EPS or abs(v[2]) > 1-EPS:
2501 if points_outside != len(f):
2502 clippedfaces.append(f)
2506 nmesh.verts.append(v)
2510 nf.col = [f.col[0]] * len(nf.v)
2512 clippedfaces.append(nf)
2513 facelist = clippedfaces[:]
2516 nmesh.faces = facelist
2521 def __simpleDepthSort(self, mesh):
2522 """Sort faces by the furthest vertex.
2524 This simple mesthod is known also as the painter algorithm, and it
2525 solves HSR correctly only for convex meshes.
2530 # The sorting requires circa n*log(n) steps
2532 progress.setActivity("HSR: Painter", n*log(n))
2534 by_furthest_z = (lambda f1, f2: progress.update() and
2535 cmp(max([v.co[2] for v in f1]), max([v.co[2] for v in f2])+EPS)
2538 # FIXME: using NMesh to sort faces. We should avoid that!
2539 nmesh = NMesh.GetRaw(mesh.name)
2541 # remember that _higher_ z values mean further points
2542 nmesh.faces.sort(by_furthest_z)
2543 nmesh.faces.reverse()
2548 def __newellDepthSort(self, mesh):
2549 """Newell's depth sorting.
2555 # Find non planar quads and convert them to triangle
2556 #for f in mesh.faces:
2558 # if is_nonplanar_quad(f.v):
2559 # print "NON QUAD??"
2563 # Now reselect all faces
2564 for f in mesh.faces:
2566 mesh.quadToTriangle()
2568 # FIXME: using NMesh to sort faces. We should avoid that!
2569 nmesh = NMesh.GetRaw(mesh.name)
2571 # remember that _higher_ z values mean further points
2572 nmesh.faces.sort(by_furthest_z)
2573 nmesh.faces.reverse()
2575 # Begin depth sort tests
2577 # use the smooth flag to set marked faces
2578 for f in nmesh.faces:
2581 facelist = nmesh.faces[:]
2585 # The steps are _at_least_ equal to len(facelist), we do not count the
2586 # feces coming out from splitting!!
2587 progress.setActivity("HSR: Newell", len(facelist))
2588 #progress.setQuiet(True)
2591 while len(facelist):
2592 debug("\n----------------------\n")
2593 debug("len(facelits): %d\n" % len(facelist))
2596 pSign = sign(P.normal[2])
2598 # We can discard faces parallel to the view vector
2599 #if P.normal[2] == 0:
2600 # facelist.remove(P)
2606 for Q in facelist[1:]:
2608 debug("P.smooth: " + str(P.smooth) + "\n")
2609 debug("Q.smooth: " + str(Q.smooth) + "\n")
2612 qSign = sign(Q.normal[2])
2613 # TODO: check also if Q is parallel??
2615 # Test 0: We need to test only those Qs whose furthest vertex
2616 # is closer to the observer than the closest vertex of P.
2618 zP = [v.co[2] for v in P.v]
2619 zQ = [v.co[2] for v in Q.v]
2620 notZOverlap = min(zP) > max(zQ) + EPS
2624 debug("NOT Z OVERLAP!\n")
2626 # If Q is not marked then we can safely print P
2629 debug("met a marked face\n")
2633 # Test 1: X extent overlapping
2634 xP = [v.co[0] for v in P.v]
2635 xQ = [v.co[0] for v in Q.v]
2636 #notXOverlap = (max(xP) <= min(xQ)) or (max(xQ) <= min(xP))
2637 notXOverlap = (min(xQ) >= max(xP)-EPS) or (min(xP) >= max(xQ)-EPS)
2641 debug("NOT X OVERLAP!\n")
2645 # Test 2: Y extent Overlapping
2646 yP = [v.co[1] for v in P.v]
2647 yQ = [v.co[1] for v in Q.v]
2648 #notYOverlap = (max(yP) <= min(yQ)) or (max(yQ) <= min(yP))
2649 notYOverlap = (min(yQ) >= max(yP)-EPS) or (min(yP) >= max(yQ)-EPS)
2653 debug("NOT Y OVERLAP!\n")
2657 # Test 3: P vertices are all behind the plane of Q
2660 d = qSign * HSR.Distance(Vector(Pi), Q)
2663 pVerticesBehindPlaneQ = (n == len(P))
2665 if pVerticesBehindPlaneQ:
2667 debug("P BEHIND Q!\n")
2671 # Test 4: Q vertices in front of the plane of P
2674 d = pSign * HSR.Distance(Vector(Qi), P)
2677 qVerticesInFrontPlaneP = (n == len(Q))
2679 if qVerticesInFrontPlaneP:
2681 debug("Q IN FRONT OF P!\n")
2685 # Test 5: Check if projections of polygons effectively overlap,
2686 # in previous tests we checked only bounding boxes.
2688 #if not projectionsOverlap(P, Q):
2689 if not ( HSR.projectionsOverlap(P, Q) or HSR.projectionsOverlap(Q, P)):
2691 debug("Projections do not overlap!\n")
2694 # We still can't say if P obscures Q.
2696 # But if Q is marked we do a face-split trying to resolve a
2697 # difficulty (maybe a visibility cycle).
2700 debug("Possibly a cycle detected!\n")
2701 debug("Split here!!\n")
2703 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2707 # The question now is: Does Q obscure P?
2710 # Test 3bis: Q vertices are all behind the plane of P
2713 d = pSign * HSR.Distance(Vector(Qi), P)
2716 qVerticesBehindPlaneP = (n == len(Q))
2718 if qVerticesBehindPlaneP:
2719 debug("\nTest 3bis\n")
2720 debug("Q BEHIND P!\n")
2723 # Test 4bis: P vertices in front of the plane of Q
2726 d = qSign * HSR.Distance(Vector(Pi), Q)
2729 pVerticesInFrontPlaneQ = (n == len(P))
2731 if pVerticesInFrontPlaneQ:
2732 debug("\nTest 4bis\n")
2733 debug("P IN FRONT OF Q!\n")
2736 # We don't even know if Q does obscure P, so they should
2737 # intersect each other, split one of them in two parts.
2738 if not qVerticesBehindPlaneP and not pVerticesInFrontPlaneQ:
2739 debug("\nSimple Intersection?\n")
2740 debug("Test 3bis or 4bis failed\n")
2741 debug("Split here!!2\n")
2743 facelist = HSR.facesplit(P, Q, facelist, nmesh)
2748 facelist.insert(0, Q)
2751 debug("Q marked!\n")
2755 if split_done == 0 and face_marked == 0:
2758 dumpfaces(maplist, "dump"+str(len(maplist)).zfill(4)+".svg")
2762 if len(facelist) == 870:
2763 dumpfaces([P, Q], "loopdebug.svg")
2766 #if facelist == None:
2768 # print [v.co for v in P]
2769 # print [v.co for v in Q]
2772 # end of while len(facelist)
2775 nmesh.faces = maplist
2776 #for f in nmesh.faces:
2782 def _doHiddenSurfaceRemoval(self, mesh):
2783 """Do HSR for the given mesh.
2785 if len(mesh.faces) == 0:
2788 if config.polygons['HSR'] == 'PAINTER':
2789 print "\nUsing the Painter algorithm for HSR."
2790 self.__simpleDepthSort(mesh)
2792 elif config.polygons['HSR'] == 'NEWELL':
2793 print "\nUsing the Newell's algorithm for HSR."
2794 self.__newellDepthSort(mesh)
2797 def _doEdgesStyle(self, mesh, edgestyleSelect):
2798 """Process Mesh Edges accroding to a given selection style.
2800 Examples of algorithms:
2803 given an edge if its adjacent faces have the same normal (that is
2804 they are complanar), than deselect it.
2807 given an edge if one its adjacent faces is frontfacing and the
2808 other is backfacing, than select it, else deselect.
2811 Mesh.Mode(Mesh.SelectModes['EDGE'])
2813 edge_cache = MeshUtils.buildEdgeFaceUsersCache(mesh)
2815 for i,edge_faces in enumerate(edge_cache):
2816 mesh.edges[i].sel = 0
2817 if edgestyleSelect(edge_faces):
2818 mesh.edges[i].sel = 1
2821 for e in mesh.edges:
2824 if edgestyleSelect(e, mesh):
2830 # ---------------------------------------------------------------------
2832 ## GUI Class and Main Program
2834 # ---------------------------------------------------------------------
2837 from Blender import BGL, Draw
2838 from Blender.BGL import *
2844 # Output Format menu
2845 output_format = config.output['FORMAT']
2846 default_value = outputWriters.keys().index(output_format)+1
2847 GUI.outFormatMenu = Draw.Create(default_value)
2848 GUI.evtOutFormatMenu = 0
2850 # Animation toggle button
2851 GUI.animToggle = Draw.Create(config.output['ANIMATION'])
2852 GUI.evtAnimToggle = 1
2854 # Join Objects toggle button
2855 GUI.joinObjsToggle = Draw.Create(config.output['JOIN_OBJECTS'])
2856 GUI.evtJoinObjsToggle = 2
2858 # Render filled polygons
2859 GUI.polygonsToggle = Draw.Create(config.polygons['SHOW'])
2861 # Shading Style menu
2862 shading_style = config.polygons['SHADING']
2863 default_value = shadingStyles.keys().index(shading_style)+1
2864 GUI.shadingStyleMenu = Draw.Create(default_value)
2865 GUI.evtShadingStyleMenu = 21
2867 GUI.evtPolygonsToggle = 3
2868 # We hide the config.polygons['EXPANSION_TRICK'], for now
2870 # Render polygon edges
2871 GUI.showEdgesToggle = Draw.Create(config.edges['SHOW'])
2872 GUI.evtShowEdgesToggle = 4
2874 # Render hidden edges
2875 GUI.showHiddenEdgesToggle = Draw.Create(config.edges['SHOW_HIDDEN'])
2876 GUI.evtShowHiddenEdgesToggle = 5
2879 edge_style = config.edges['STYLE']
2880 default_value = edgeStyles.keys().index(edge_style)+1
2881 GUI.edgeStyleMenu = Draw.Create(default_value)
2882 GUI.evtEdgeStyleMenu = 6
2885 GUI.edgeWidthSlider = Draw.Create(config.edges['WIDTH'])
2886 GUI.evtEdgeWidthSlider = 7
2889 c = config.edges['COLOR']
2890 GUI.edgeColorPicker = Draw.Create(c[0]/255.0, c[1]/255.0, c[2]/255.0)
2891 GUI.evtEdgeColorPicker = 71
2894 GUI.evtRenderButton = 8
2897 GUI.evtExitButton = 9
2901 # initialize static members
2904 glClear(GL_COLOR_BUFFER_BIT)
2905 glColor3f(0.0, 0.0, 0.0)
2906 glRasterPos2i(10, 350)
2907 Draw.Text("VRM: Vector Rendering Method script. Version %s." %
2909 glRasterPos2i(10, 335)
2910 Draw.Text("Press Q or ESC to quit.")
2912 # Build the output format menu
2913 glRasterPos2i(10, 310)
2914 Draw.Text("Select the output Format:")
2915 outMenuStruct = "Output Format %t"
2916 for t in outputWriters.keys():
2917 outMenuStruct = outMenuStruct + "|%s" % t
2918 GUI.outFormatMenu = Draw.Menu(outMenuStruct, GUI.evtOutFormatMenu,
2919 10, 285, 160, 18, GUI.outFormatMenu.val, "Choose the Output Format")
2922 GUI.animToggle = Draw.Toggle("Animation", GUI.evtAnimToggle,
2923 10, 260, 160, 18, GUI.animToggle.val,
2924 "Toggle rendering of animations")
2926 # Join Objects toggle
2927 GUI.joinObjsToggle = Draw.Toggle("Join objects", GUI.evtJoinObjsToggle,
2928 10, 235, 160, 18, GUI.joinObjsToggle.val,
2929 "Join objects in the rendered file")
2932 Draw.Button("Render", GUI.evtRenderButton, 10, 210-25, 75, 25+18,
2934 Draw.Button("Exit", GUI.evtExitButton, 95, 210-25, 75, 25+18, "Exit!")
2937 glRasterPos2i(200, 310)
2938 Draw.Text("Rendering Style:")
2941 GUI.polygonsToggle = Draw.Toggle("Filled Polygons", GUI.evtPolygonsToggle,
2942 200, 285, 160, 18, GUI.polygonsToggle.val,
2943 "Render filled polygons")
2945 if GUI.polygonsToggle.val == 1:
2947 # Polygon Shading Style
2948 shadingStyleMenuStruct = "Shading Style %t"
2949 for t in shadingStyles.keys():
2950 shadingStyleMenuStruct = shadingStyleMenuStruct + "|%s" % t.lower()
2951 GUI.shadingStyleMenu = Draw.Menu(shadingStyleMenuStruct, GUI.evtShadingStyleMenu,
2952 200, 260, 160, 18, GUI.shadingStyleMenu.val,
2953 "Choose the shading style")
2957 GUI.showEdgesToggle = Draw.Toggle("Show Edges", GUI.evtShowEdgesToggle,
2958 200, 235, 160, 18, GUI.showEdgesToggle.val,
2959 "Render polygon edges")
2961 if GUI.showEdgesToggle.val == 1:
2964 edgeStyleMenuStruct = "Edge Style %t"
2965 for t in edgeStyles.keys():
2966 edgeStyleMenuStruct = edgeStyleMenuStruct + "|%s" % t.lower()
2967 GUI.edgeStyleMenu = Draw.Menu(edgeStyleMenuStruct, GUI.evtEdgeStyleMenu,
2968 200, 210, 160, 18, GUI.edgeStyleMenu.val,
2969 "Choose the edge style")
2972 GUI.edgeWidthSlider = Draw.Slider("Width: ", GUI.evtEdgeWidthSlider,
2973 200, 185, 140, 18, GUI.edgeWidthSlider.val,
2974 0.0, 10.0, 0, "Change Edge Width")
2977 GUI.edgeColorPicker = Draw.ColorPicker(GUI.evtEdgeColorPicker,
2978 342, 185, 18, 18, GUI.edgeColorPicker.val, "Choose Edge Color")
2981 GUI.showHiddenEdgesToggle = Draw.Toggle("Show Hidden Edges",
2982 GUI.evtShowHiddenEdgesToggle,
2983 200, 160, 160, 18, GUI.showHiddenEdgesToggle.val,
2984 "Render hidden edges as dashed lines")
2986 glRasterPos2i(10, 160)
2987 Draw.Text("%s (c) 2006" % __author__)
2989 def event(evt, val):
2991 if evt == Draw.ESCKEY or evt == Draw.QKEY:
2998 def button_event(evt):
3000 if evt == GUI.evtExitButton:
3003 elif evt == GUI.evtOutFormatMenu:
3004 i = GUI.outFormatMenu.val - 1
3005 config.output['FORMAT']= outputWriters.keys()[i]
3006 # Set the new output file
3008 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3010 elif evt == GUI.evtAnimToggle:
3011 config.output['ANIMATION'] = bool(GUI.animToggle.val)
3013 elif evt == GUI.evtJoinObjsToggle:
3014 config.output['JOIN_OBJECTS'] = bool(GUI.joinObjsToggle.val)
3016 elif evt == GUI.evtPolygonsToggle:
3017 config.polygons['SHOW'] = bool(GUI.polygonsToggle.val)
3019 elif evt == GUI.evtShadingStyleMenu:
3020 i = GUI.shadingStyleMenu.val - 1
3021 config.polygons['SHADING'] = shadingStyles.keys()[i]
3023 elif evt == GUI.evtShowEdgesToggle:
3024 config.edges['SHOW'] = bool(GUI.showEdgesToggle.val)
3026 elif evt == GUI.evtShowHiddenEdgesToggle:
3027 config.edges['SHOW_HIDDEN'] = bool(GUI.showHiddenEdgesToggle.val)
3029 elif evt == GUI.evtEdgeStyleMenu:
3030 i = GUI.edgeStyleMenu.val - 1
3031 config.edges['STYLE'] = edgeStyles.keys()[i]
3033 elif evt == GUI.evtEdgeWidthSlider:
3034 config.edges['WIDTH'] = float(GUI.edgeWidthSlider.val)
3036 elif evt == GUI.evtEdgeColorPicker:
3037 config.edges['COLOR'] = [int(c*255.0) for c in GUI.edgeColorPicker.val]
3039 elif evt == GUI.evtRenderButton:
3040 label = "Save %s" % config.output['FORMAT']
3041 # Show the File Selector
3043 Blender.Window.FileSelector(vectorize, label, outputfile)
3046 print "Event: %d not handled!" % evt
3053 from pprint import pprint
3055 pprint(config.output)
3056 pprint(config.polygons)
3057 pprint(config.edges)
3059 _init = staticmethod(_init)
3060 draw = staticmethod(draw)
3061 event = staticmethod(event)
3062 button_event = staticmethod(button_event)
3063 conf_debug = staticmethod(conf_debug)
3065 # A wrapper function for the vectorizing process
3066 def vectorize(filename):
3067 """The vectorizing process is as follows:
3069 - Instanciate the writer and the renderer
3074 print "\nERROR: invalid file name!"
3077 from Blender import Window
3078 editmode = Window.EditMode()
3079 if editmode: Window.EditMode(0)
3081 actualWriter = outputWriters[config.output['FORMAT']]
3082 writer = actualWriter(filename)
3084 renderer = Renderer()
3085 renderer.doRendering(writer, config.output['ANIMATION'])
3087 if editmode: Window.EditMode(1)
3092 if __name__ == "__main__":
3097 basename = Blender.sys.basename(Blender.Get('filename'))
3099 outputfile = Blender.sys.splitext(basename)[0] + "." + str(config.output['FORMAT']).lower()
3101 if Blender.mode == 'background':
3102 progress = ConsoleProgressIndicator()
3103 vectorize(outputfile)
3105 progress = GraphicalProgressIndicator()
3106 Draw.Register(GUI.draw, GUI.event, GUI.button_event)