6 Tooltip: 'Vector Rendering Method Export Script 0.3'
9 # ---------------------------------------------------------------------
10 # Copyright (c) 2006 Antonio Ospite
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 # ---------------------------------------------------------------------
28 # NOTE: I do not know who is the original author of 'vrm'.
29 # The present code is almost entirely rewritten from scratch,
30 # but if I have to give credits to anyone, please let me know,
31 # so I can update the copyright.
33 # ---------------------------------------------------------------------
36 # Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
37 # Thanks to Anthony C. D'Agostino for the original backface.py script
39 # ---------------------------------------------------------------------
42 from Blender import Scene, Object, NMesh, Lamp, Camera
43 from Blender.Mathutils import *
47 # ---------------------------------------------------------------------
49 ## Projections classes
51 # ---------------------------------------------------------------------
54 """Calculate the projection of an object given the camera.
56 A projector is useful to so some per-object transformation to obtain the
57 projection of an object given the camera.
59 The main method is #doProjection# see the method description for the
63 def __init__(self, cameraObj, obMesh, canvasSize):
64 """Calculate the projection matrix.
66 The projection matrix depends, in this case, on the camera settings,
67 and also on object transformation matrix.
70 self.size = canvasSize
72 camera = cameraObj.getData()
74 aspect = float(canvasSize[0])/float(canvasSize[1])
75 near = camera.clipStart
78 fovy = atan(0.5/aspect/(camera.lens/32))
81 # What projection do we want?
83 m2 = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale)
85 m2 = self._calcPerspectiveMatrix(fovy, aspect, near, far)
89 cam = Matrix(cameraObj.getInverseMatrix())
92 m1 = Matrix(obMesh.getMatrix())
98 self.projectionMatrix = mP
104 def doProjection(self, v):
105 """Project the point on the view plane.
107 Given a vertex calculate the projection using the current projection
111 # Note that we need the vertex expressed using homogeneous coordinates
112 p = self.projectionMatrix * Vector([v[0], v[1], v[2], 1.0])
118 p[0] = round(p[0]*mW)+mW
119 p[1] = round(p[1]*mH)+mH
121 p[0] = round((p[0]/p[3])*mW)+mW
122 p[1] = round((p[1]/p[3])*mH)+mH
124 # For now we want (0,0) in the top-left corner of the canvas
125 # Mirror and translate along y
135 def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
136 """Return a perspective projection matrix."""
138 top = near * tan(fovy * pi / 360.0)
142 x = (2.0 * near) / (right-left)
143 y = (2.0 * near) / (top-bottom)
144 a = (right+left) / (right-left)
145 b = (top+bottom) / (top - bottom)
146 c = - ((far+near) / (far-near))
147 d = - ((2*far*near)/(far-near))
153 [0.0, 0.0, -1.0, 0.0])
157 def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
158 """Return an orthogonal projection matrix."""
160 top = near * tan(fovy * pi / 360.0) * (scale * 10)
162 left = bottom * aspect
167 tx = -((right+left)/rl)
168 ty = -((top+bottom)/tb)
172 [2.0/rl, 0.0, 0.0, tx],
173 [0.0, 2.0/tb, 0.0, ty],
174 [0.0, 0.0, 2.0/fn, tz],
175 [0.0, 0.0, 0.0, 1.0])
180 # ---------------------------------------------------------------------
182 ## Object representation class
184 # ---------------------------------------------------------------------
186 # TODO: a class to represent the needed properties of a 2D vector image
187 # Just use a NMesh structure?
190 # ---------------------------------------------------------------------
192 ## Vector Drawing Classes
194 # ---------------------------------------------------------------------
200 A class for printing output in a vectorial format.
202 Given a 2D representation of the 3D scene the class is responsible to
203 write it is a vector format.
205 Every subclasses of VectorWriter must have at last the following public
207 - printCanvas(mesh) --- where mesh is as specified before.
210 def __init__(self, fileName, canvasSize):
211 """Open the file named #fileName# and set the canvas size."""
213 self.file = open(fileName, "w")
214 print "Outputting to: ", fileName
216 self.canvasSize = canvasSize
223 def printCanvas(mesh):
239 class SVGVectorWriter(VectorWriter):
240 """A concrete class for writing SVG output.
242 The class does not support animations, yet.
246 def __init__(self, file, canvasSize):
247 """Simply call the parent Contructor."""
248 VectorWriter.__init__(self, file, canvasSize)
255 def printCanvas(self, scene):
256 """Convert the scene representation to SVG."""
260 Objects = scene.getChildren()
262 self.file.write("<g>\n")
264 for face in obj.getData().faces:
265 self._printPolygon(face)
267 self._printWireframe(obj.getData())
269 self.file.write("</g>\n")
277 def _printHeader(self):
278 """Print SVG header."""
280 self.file.write("<?xml version=\"1.0\"?>\n")
281 self.file.write("<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n")
282 self.file.write("\t\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n")
283 self.file.write("<svg version=\"1.1\"\n")
284 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
285 self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
288 def _printFooter(self):
289 """Print the SVG footer."""
291 self.file.write("\n</svg>\n")
294 def _printWireframe(self, mesh):
295 """Print the wireframe using mesh edges... is this the correct way?
303 stroke_col = [0, 0, 0]
305 self.file.write("<g>\n")
308 self.file.write("<line x1=\"%g\" y1=\"%g\" x2=\"%g\" y2=\"%g\"\n"
309 % ( e.v1[0], e.v1[1], e.v2[0], e.v2[1] ) )
310 self.file.write(" style=\"stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
311 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
312 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
313 self.file.write("\"/>\n")
315 self.file.write("</g>\n")
319 def _printPolygon(self, face):
320 """Print our primitive, finally.
327 self.file.write("<polygon points=\"")
330 self.file.write("%g,%g " % (v[0], v[1]))
332 self.file.seek(-1,1) # get rid of the last space
333 self.file.write("\"\n")
335 #take as face color the first vertex color
337 color = [fcol.r, fcol.g, fcol.b]
339 stroke_col = [0, 0, 0]
343 self.file.write("\tstyle=\"fill:rgb("+str(color[0])+","+str(color[1])+","+str(color[2])+");")
344 self.file.write(" stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
345 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
346 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
347 self.file.write("\"/>\n")
350 # ---------------------------------------------------------------------
354 # ---------------------------------------------------------------------
356 def RotatePoint(PX,PY,PZ,AngleX,AngleY,AngleZ):
360 NewY = (PY * cos(AngleX))-(PZ * sin(AngleX))
361 NewZ = (PZ * cos(AngleX))+(PY * sin(AngleX))
365 NewZ = (PZ * cos(AngleY))-(PX * sin(AngleY))
366 NewX = (PX * cos(AngleY))+(PZ * sin(AngleY))
370 NewX = (PX * cos(AngleZ))-(PY * sin(AngleZ))
371 NewY = (PY * cos(AngleZ))+(PX * sin(AngleZ))
372 NewPoint.append(NewX)
373 NewPoint.append(NewY)
374 NewPoint.append(NewZ)
378 """Render a scene viewed from a given camera.
380 This class is responsible of the rendering process, hence transormation
381 and projection of the ojects in the scene are invoked by the renderer.
383 The user can optionally provide a specific camera for the rendering, see
384 the #doRendering# method for more informations.
388 """Set the canvas size to a defaulr value.
390 The only instance attribute here is the canvas size, which can be
391 queryed to the renderer by other entities.
393 self.canvasSize = (0.0, 0.0)
400 def getCanvasSize(self):
401 """Return the current canvas size read from Blender rendering context"""
402 return self.canvasSize
404 def doRendering(self, scene, cameraObj=None):
405 """Control the rendering process.
407 Here we control the entire rendering process invoking the operation
408 needed to transforma project the 3D scene in two dimensions.
411 scene --- the Blender Scene to render
412 cameraObj --- the camera object to use for the viewing processing
415 if cameraObj == None:
416 cameraObj = scene.getCurrentCamera()
418 context = scene.getRenderingContext()
419 self.canvasSize = (context.imageSizeX(), context.imageSizeY())
421 Objects = scene.getChildren()
423 # A structure to store the transformed scene
424 newscene = Scene.New("flat"+scene.name)
428 if (obj.getType() != "Mesh"):
429 print "Type:", obj.getType(), "\tSorry, only mesh Object supported!"
432 # Get a projector for this object
433 proj = Projector(cameraObj, obj, self.canvasSize)
435 # Let's store the transformed data
436 transformed_mesh = NMesh.New("flat"+obj.name)
437 transformed_mesh.hasVertexColours(1)
440 for v in obj.getData().verts:
441 transformed_mesh.verts.append(v)
442 transformed_mesh.edges = self._processEdges(obj.getData().edges)
443 print transformed_mesh.edges
446 # Store the materials
447 materials = obj.getData().getMaterials()
449 meshfaces = obj.getData().faces
451 for face in meshfaces:
453 # if the face is visible flatten it on the "picture plane"
454 if self._isFaceVisible_old(face, obj, cameraObj):
456 # Store transformed face
457 newface = NMesh.Face()
461 p = proj.doProjection(vert.co)
463 tmp_vert = NMesh.Vert(p[0], p[1], p[2])
465 # Add the vert to the mesh
466 transformed_mesh.verts.append(tmp_vert)
468 newface.v.append(tmp_vert)
471 # Per-face color calculation
472 # code taken mostly from the original vrm script
473 # TODO: understand the code and rewrite it clearly
476 fakelight = Object.Get("Lamp").loc
477 if fakelight == None:
478 fakelight = [1.0, 1.0, -0.3]
480 norm = Vector(face.no)
481 vektori = (norm[0]*fakelight[0]+norm[1]*fakelight[1]+norm[2]*fakelight[2])
482 vduzine = fabs(sqrt(pow(norm[0],2)+pow(norm[1],2)+pow(norm[2],2))*sqrt(pow(fakelight[0],2)+pow(fakelight[1],2)+pow(fakelight[2],2)))
483 intensity = floor(ambient + 200*acos(vektori/vduzine))/200
488 tmp_col = materials[face.mat].getRGBCol()
490 tmp_col = [0.5, 0.5, 0.5]
492 tmp_col = [ (c>intensity) and int(round((c-intensity)*10)*25.5) for c in tmp_col ]
494 vcol = NMesh.Col(tmp_col[0], tmp_col[1], tmp_col[2])
495 newface.col = [vcol, vcol, vcol, 255]
497 transformed_mesh.addFace(newface)
499 # at the end of the loop on obj
501 transformed_obj = Object.New(obj.getType(), "flat"+obj.name)
502 transformed_obj.link(transformed_mesh)
503 transformed_obj.loc = obj.loc
504 newscene.link(transformed_obj)
514 def _isFaceVisible_old(self, face, obj, cameraObj):
515 """Determine if the face is visible from the current camera.
517 The following code is taken basicly from the original vrm script.
526 # translate and rotate according to the object matrix
527 # and then translate according to the camera position
531 #a = m*Vector(face[0]) - Vector(cameraObj.loc)
532 #b = m*Vector(face[1]) - Vector(cameraObj.loc)
533 #c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
539 a = RotatePoint(a[0], a[1], a[2], obj.RotX, obj.RotY, obj.RotZ)
540 a[0] += obj.LocX - camera.LocX
541 a[1] += obj.LocY - camera.LocY
542 a[2] += obj.LocZ - camera.LocZ
547 b = RotatePoint(b[0], b[1], b[2], obj.RotX, obj.RotY, obj.RotZ)
548 b[0] += obj.LocX - camera.LocX
549 b[1] += obj.LocY - camera.LocY
550 b[2] += obj.LocZ - camera.LocZ
552 c.append(face[numvert-1][0])
553 c.append(face[numvert-1][1])
554 c.append(face[numvert-1][2])
555 c = RotatePoint(c[0], c[1], c[2], obj.RotX, obj.RotY, obj.RotZ)
556 c[0] += obj.LocX - camera.LocX
557 c[1] += obj.LocY - camera.LocY
558 c[2] += obj.LocZ - camera.LocZ
561 norm[0] = (b[1] - a[1])*(c[2] - a[2]) - (c[1] - a[1])*(b[2] - a[2])
562 norm[1] = -((b[0] - a[0])*(c[2] - a[2]) - (c[0] - a[0])*(b[2] - a[2]))
563 norm[2] = (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])
565 d = norm[0]*a[0] + norm[1]*a[1] + norm[2]*a[2]
566 #d = DotVecs(Vector(norm), Vector(a))
570 def _isFaceVisible(self, face, obj, cameraObj):
571 """Determine if the face is visible from the current camera.
573 The following code is taken basicly from the original vrm script.
582 # translate and rotate according to the object matrix
583 # and then translate according to the camera position
587 a = m*Vector(face[0]) - Vector(cameraObj.loc)
588 b = m*Vector(face[1]) - Vector(cameraObj.loc)
589 c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
591 norm = m*Vector(face.no)
604 def _doVisibleSurfaceDetermination(object):
607 def _doColorizing(object):
610 def _doStylizingEdges(self, object, style):
611 """Process Mesh Edges. (For now copy the edge data, in next version it
612 can be a place where recognize silouhettes and/or contours).
615 return: a processed edge list
621 # ---------------------------------------------------------------------
625 # ---------------------------------------------------------------------
628 # FIXME: really hackish code, just to test if the other parts work
629 def depthSorting(scene):
631 cameraObj = Scene.GetCurrent().getCurrentCamera()
632 Objects = scene.getChildren()
634 Objects.sort(lambda obj1, obj2:
635 cmp(Vector(Vector(cameraObj.loc) - Vector(obj1.loc)).length,
636 Vector(Vector(cameraObj.loc) - Vector(obj2.loc)).length
640 # hackish sorting of faces according to the max z value of a vertex
646 # Sort faces according to the min z coordinate in a face
647 #cmp(min([v[2] for v in f1]), min([v[2] for v in f2])))
649 # Sort faces according to the max z coordinate in a face
650 cmp(max([v[2] for v in f1]), max([v[2] for v in f2])))
652 # Sort faces according to the avg z coordinate in a face
653 #cmp(sum([v[2] for v in f1])/len(f1), sum([v[2] for v in f2])/len(f2)))
658 for o in scene.getChildren():
663 def vectorize(filename):
664 """The vectorizing process is as follows:
670 If you want to render an animation the second pass should be
671 repeated for any frame, and the frame number should be passed to the
675 print "Filename: %s" % filename
677 scene = Scene.GetCurrent()
678 renderer = Renderer()
680 flatScene = renderer.doRendering(scene)
681 canvasSize = renderer.getCanvasSize()
683 depthSorting(flatScene)
685 writer = SVGVectorWriter(filename, canvasSize)
686 writer.printCanvas(flatScene)
688 Blender.Scene.unlink(flatScene)
692 if __name__ == "__main__":
693 # with this trick we can run the script in batch mode
695 Blender.Window.FileSelector (vectorize, 'Save SVG', "proba.svg")
697 vectorize("proba.svg")