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 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 ## Mesh 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.file.write("</g>\n")
275 def _printHeader(self):
276 """Print SVG header."""
278 self.file.write("<?xml version=\"1.0\"?>\n")
279 self.file.write("<svg version=\"1.2\"\n")
280 self.file.write("\txmlns=\"http://www.w3.org/2000/svg\"\n")
281 self.file.write("\twidth=\"%d\" height=\"%d\" streamable=\"true\">\n\n" %
284 def _printFooter(self):
285 """Print the SVG footer."""
287 self.file.write("\n</svg>\n")
290 def _printPolygon(self, face):
291 """Print our primitive, finally.
298 self.file.write("<polygon points=\"")
301 self.file.write("%g,%g " % (v[0], v[1]))
303 self.file.seek(-1,1) # get rid of the last space
304 self.file.write("\"\n")
306 #take as face color the first vertex color
308 color = [fcol.r, fcol.g, fcol.b]
310 stroke_col = [0, 0, 0]
314 self.file.write("\tstyle=\"fill:rgb("+str(color[0])+","+str(color[1])+","+str(color[2])+");")
315 self.file.write(" stroke:rgb("+str(stroke_col[0])+","+str(stroke_col[1])+","+str(stroke_col[2])+");")
316 self.file.write(" stroke-width:"+str(stroke_width)+";\n")
317 self.file.write(" stroke-linecap:round;stroke-linejoin:round")
318 self.file.write("\"/>\n")
321 # ---------------------------------------------------------------------
325 # ---------------------------------------------------------------------
327 def RotatePoint(PX,PY,PZ,AngleX,AngleY,AngleZ):
331 NewY = (PY * cos(AngleX))-(PZ * sin(AngleX))
332 NewZ = (PZ * cos(AngleX))+(PY * sin(AngleX))
336 NewZ = (PZ * cos(AngleY))-(PX * sin(AngleY))
337 NewX = (PX * cos(AngleY))+(PZ * sin(AngleY))
341 NewX = (PX * cos(AngleZ))-(PY * sin(AngleZ))
342 NewY = (PY * cos(AngleZ))+(PX * sin(AngleZ))
343 NewPoint.append(NewX)
344 NewPoint.append(NewY)
345 NewPoint.append(NewZ)
349 """Render a scene viewed from a given camera.
351 This class is responsible of the rendering process, hence transormation
352 and projection of the ojects in the scene are invoked by the renderer.
354 The user can optionally provide a specific camera for the rendering, see
355 the #doRendering# method for more informations.
359 """Set the canvas size to a defaulr value.
361 The only instance attribute here is the canvas size, which can be
362 queryed to the renderer by other entities.
364 self.canvasSize = (0.0, 0.0)
371 def getCanvasSize(self):
372 """Return the current canvas size read from Blender rendering context"""
373 return self.canvasSize
375 def doRendering(self, scene, cameraObj=None):
376 """Control the rendering process.
378 Here we control the entire rendering process invoking the operation
379 needed to transforma project the 3D scene in two dimensions.
382 scene --- the Blender Scene to render
383 cameraObj --- the camera object to use for the viewing processing
386 if cameraObj == None:
387 cameraObj = scene.getCurrentCamera()
389 context = scene.getRenderingContext()
390 self.canvasSize = (context.imageSizeX(), context.imageSizeY())
392 Objects = scene.getChildren()
394 # A structure to store the transformed scene
395 newscene = Scene.New("flat"+scene.name)
399 if (obj.getType() != "Mesh"):
400 print "Type:", obj.getType(), "\tSorry, only mesh Object supported!"
403 # Get a projector for this object
404 proj = Projector(cameraObj, obj, self.canvasSize)
406 # Let's store the transformed data
407 transformed_mesh = NMesh.New("flat"+obj.name)
408 transformed_mesh.hasVertexColours(1)
410 # Store the materials
411 materials = obj.getData().getMaterials()
413 meshfaces = obj.getData().faces
415 for face in meshfaces:
417 # if the face is visible flatten it on the "picture plane"
418 if self._isFaceVisible_old(face, obj, cameraObj):
420 # Store transformed face
421 newface = NMesh.Face()
425 p = proj.doProjection(vert.co)
427 tmp_vert = NMesh.Vert(p[0], p[1], p[2])
429 # Add the vert to the mesh
430 transformed_mesh.verts.append(tmp_vert)
432 newface.v.append(tmp_vert)
435 # Per-face color calculation
436 # code taken mostly from the original vrm script
437 # TODO: understand the code and rewrite it clearly
440 fakelight = Object.Get("Lamp").loc
441 if fakelight == None:
442 fakelight = [1.0, 1.0, -0.3]
444 norm = Vector(face.no)
445 vektori = (norm[0]*fakelight[0]+norm[1]*fakelight[1]+norm[2]*fakelight[2])
446 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)))
447 intensity = floor(ambient + 200*acos(vektori/vduzine))/200
452 tmp_col = materials[face.mat].getRGBCol()
454 tmp_col = [0.5, 0.5, 0.5]
456 tmp_col = [ (c>intensity) and int(round((c-intensity)*10)*25.5) for c in tmp_col ]
458 vcol = NMesh.Col(tmp_col[0], tmp_col[1], tmp_col[2])
459 newface.col = [vcol, vcol, vcol, 255]
461 transformed_mesh.addFace(newface)
463 # at the end of the loop on obj
465 transformed_obj = Object.New(obj.getType(), "flat"+obj.name)
466 transformed_obj.link(transformed_mesh)
467 transformed_obj.loc = obj.loc
468 newscene.link(transformed_obj)
478 def _isFaceVisible_old(self, face, obj, cameraObj):
479 """Determine if the face is visible from the current camera.
481 The following code is taken basicly from the original vrm script.
490 # translate and rotate according to the object matrix
491 # and then translate according to the camera position
495 #a = m*Vector(face[0]) - Vector(cameraObj.loc)
496 #b = m*Vector(face[1]) - Vector(cameraObj.loc)
497 #c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
503 a = RotatePoint(a[0], a[1], a[2], obj.RotX, obj.RotY, obj.RotZ)
504 a[0] += obj.LocX - camera.LocX
505 a[1] += obj.LocY - camera.LocY
506 a[2] += obj.LocZ - camera.LocZ
511 b = RotatePoint(b[0], b[1], b[2], obj.RotX, obj.RotY, obj.RotZ)
512 b[0] += obj.LocX - camera.LocX
513 b[1] += obj.LocY - camera.LocY
514 b[2] += obj.LocZ - camera.LocZ
516 c.append(face[numvert-1][0])
517 c.append(face[numvert-1][1])
518 c.append(face[numvert-1][2])
519 c = RotatePoint(c[0], c[1], c[2], obj.RotX, obj.RotY, obj.RotZ)
520 c[0] += obj.LocX - camera.LocX
521 c[1] += obj.LocY - camera.LocY
522 c[2] += obj.LocZ - camera.LocZ
525 norm[0] = (b[1] - a[1])*(c[2] - a[2]) - (c[1] - a[1])*(b[2] - a[2])
526 norm[1] = -((b[0] - a[0])*(c[2] - a[2]) - (c[0] - a[0])*(b[2] - a[2]))
527 norm[2] = (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])
529 d = norm[0]*a[0] + norm[1]*a[1] + norm[2]*a[2]
530 #d = DotVecs(Vector(norm), Vector(a))
534 def _isFaceVisible(self, face, obj, cameraObj):
535 """Determine if the face is visible from the current camera.
537 The following code is taken basicly from the original vrm script.
546 # translate and rotate according to the object matrix
547 # and then translate according to the camera position
551 a = m*Vector(face[0]) - Vector(cameraObj.loc)
552 b = m*Vector(face[1]) - Vector(cameraObj.loc)
553 c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
555 norm = m*Vector(face.no)
561 def _doClipping(face):
565 # ---------------------------------------------------------------------
569 # ---------------------------------------------------------------------
572 # FIXME: really hackish code, just to test if the other parts work
573 def depthSorting(scene):
575 cameraObj = Scene.GetCurrent().getCurrentCamera()
576 Objects = scene.getChildren()
578 Objects.sort(lambda obj1, obj2:
579 cmp(Vector(Vector(cameraObj.loc) - Vector(obj1.loc)).length,
580 Vector(Vector(cameraObj.loc) - Vector(obj2.loc)).length
584 # hackish sorting of faces according to the max z value of a vertex
590 # Sort faces according to the min z coordinate in a face
591 #cmp(min([v[2] for v in f1]), min([v[2] for v in f2])))
593 # Sort faces according to the max z coordinate in a face
594 cmp(max([v[2] for v in f1]), max([v[2] for v in f2])))
596 # Sort faces according to the avg z coordinate in a face
597 #cmp(sum([v[2] for v in f1])/len(f1), sum([v[2] for v in f2])/len(f2)))
602 for o in scene.getChildren():
607 def vectorize(filename):
609 print "Filename: %s" % filename
611 scene = Scene.GetCurrent()
612 renderer = Renderer()
614 flatScene = renderer.doRendering(scene)
615 canvasSize = renderer.getCanvasSize()
617 depthSorting(flatScene)
619 writer = SVGVectorWriter(filename, canvasSize)
620 writer.printCanvas(flatScene)
622 Blender.Scene.unlink(flatScene)
626 if __name__ == "__main__":
628 Blender.Window.FileSelector (vectorize, 'Save SVG', "proba.svg")
630 vectorize("proba.svg")