#!BPY
"""
Name: 'VRM'
Blender: 241
Group: 'Export'
Tooltip: 'Vector Rendering Method Export Script 0.3'
"""
# ---------------------------------------------------------------------
# Copyright (c) 2006 Antonio Ospite
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
# ---------------------------------------------------------------------
#
# NOTE: I do not know who is the original author of 'vrm'.
# The present code is almost entirely rewritten from scratch,
# but if I have to give credits to anyone, please let me know,
# so I can update the copyright.
#
# ---------------------------------------------------------------------
#
# Additional credits:
# Thanks to Emilio Aguirre for S2flender from which I took inspirations :)
# Thanks to Anthony C. D'Agostino for the backface.py script
#
# ---------------------------------------------------------------------
import Blender
from Blender import Scene, Object, NMesh, Lamp, Camera
from Blender.Mathutils import *
from math import *
# ---------------------------------------------------------------------
#
## Projections classes
#
# ---------------------------------------------------------------------
class Projector:
"""Calculate the projection of an object given the camera.
A projector is useful to so some per-object transformation to obtain the
projection of an object given the camera.
The main method is #doProjection# see the method description for the
parameter list.
"""
def __init__(self, cameraObj, obMesh, canvasSize):
"""Calculate the projection matrix.
The projection matrix depends, in this case, on the camera settings,
and also on object transformation matrix.
"""
self.size = canvasSize
camera = cameraObj.getData()
aspect = float(canvasSize[0])/float(canvasSize[1])
near = camera.clipStart
far = camera.clipEnd
fovy = atan(0.5/aspect/(camera.lens/32))
fovy = fovy * 360/pi
# What projection do we want?
if camera.type:
m2 = self._calcOrthoMatrix(fovy, aspect, near, far, 17) #camera.scale)
else:
m2 = self._calcPerspectiveMatrix(fovy, aspect, near, far)
# View transformation
cam = Matrix(cameraObj.getInverseMatrix())
cam.transpose()
m1 = Matrix(obMesh.getMatrix())
m1.transpose()
mP = cam * m1
mP = m2 * mP
self.projectionMatrix = mP
##
# Public methods
#
def doProjection(self, v):
"""Project the point on the view plane.
Given a vertex calculate the projection using the current projection
matrix.
"""
# Note that we need the vertex expressed using homogeneous coordinates
p = self.projectionMatrix * Vector([v[0], v[1], v[2], 1.0])
mW = self.size[0]/2
mH = self.size[1]/2
if p[3]<=0:
p[0] = round(p[0]*mW)+mW
p[1] = round(p[1]*mH)+mH
else:
p[0] = round((p[0]/p[3])*mW)+mW
p[1] = round((p[1]/p[3])*mH)+mH
# For now we want (0,0) in the top-left corner of the canvas
# Mirror and translate along y
p[1] *= -1
p[1] += self.size[1]
return p
##
# Private methods
#
def _calcPerspectiveMatrix(self, fovy, aspect, near, far):
"""Return a perspective projection matrix."""
top = near * tan(fovy * pi / 360.0)
bottom = -top
left = bottom*aspect
right= top*aspect
x = (2.0 * near) / (right-left)
y = (2.0 * near) / (top-bottom)
a = (right+left) / (right-left)
b = (top+bottom) / (top - bottom)
c = - ((far+near) / (far-near))
d = - ((2*far*near)/(far-near))
m = Matrix(
[x, 0.0, a, 0.0],
[0.0, y, b, 0.0],
[0.0, 0.0, c, d],
[0.0, 0.0, -1.0, 0.0])
return m
def _calcOrthoMatrix(self, fovy, aspect , near, far, scale):
"""Return an orthogonal projection matrix."""
top = near * tan(fovy * pi / 360.0) * (scale * 10)
bottom = -top
left = bottom * aspect
right= top * aspect
rl = right-left
tb = top-bottom
fn = near-far
tx = -((right+left)/rl)
ty = -((top+bottom)/tb)
tz = ((far+near)/fn)
m = Matrix(
[2.0/rl, 0.0, 0.0, tx],
[0.0, 2.0/tb, 0.0, ty],
[0.0, 0.0, 2.0/fn, tz],
[0.0, 0.0, 0.0, 1.0])
return m
# ---------------------------------------------------------------------
#
## Mesh representation class
#
# ---------------------------------------------------------------------
# TODO: a class to represent the needed properties of a 2D vector image
# Just use a NMesh structure?
# ---------------------------------------------------------------------
#
## Vector Drawing Classes
#
# ---------------------------------------------------------------------
## A generic Writer
class VectorWriter:
"""
A class for printing output in a vectorial format.
Given a 2D representation of the 3D scene the class is responsible to
write it is a vector format.
Every subclasses of VectorWriter must have at last the following public
methods:
- printCanvas(mesh) --- where mesh is as specified before.
"""
def __init__(self, fileName, canvasSize):
"""Open the file named #fileName# and set the canvas size."""
self.file = open(fileName, "w")
print "Outputting to: ", fileName
self.canvasSize = canvasSize
##
# Public Methods
#
def printCanvas(mesh):
return
##
# Private Methods
#
def _printHeader():
return
def _printFooter():
return
## SVG Writer
class SVGVectorWriter(VectorWriter):
"""A concrete class for writing SVG output.
The class does not support animations, yet.
Sorry.
"""
def __init__(self, file, canvasSize):
"""Simply call the parent Contructor."""
VectorWriter.__init__(self, file, canvasSize)
##
# Public Methods
#
def printCanvas(self, scene):
"""Convert the scene representation to SVG."""
self._printHeader()
Objects = scene.getChildren()
for obj in Objects:
self.file.write("\n")
for face in obj.getData().faces:
self._printPolygon(face)
self.file.write("\n")
self._printFooter()
##
# Private Methods
#
def _printHeader(self):
"""Print SVG header."""
self.file.write("\n")
self.file.write("\n")
self.file.close()
def _printPolygon(self, face):
"""Print our primitive, finally.
"""
wireframe = False
stroke_width=0.5
self.file.write("\n")
# ---------------------------------------------------------------------
#
## Rendering Classes
#
# ---------------------------------------------------------------------
def RotatePoint(PX,PY,PZ,AngleX,AngleY,AngleZ):
NewPoint = []
# Rotate X
NewY = (PY * cos(AngleX))-(PZ * sin(AngleX))
NewZ = (PZ * cos(AngleX))+(PY * sin(AngleX))
# Rotate Y
PZ = NewZ
PY = NewY
NewZ = (PZ * cos(AngleY))-(PX * sin(AngleY))
NewX = (PX * cos(AngleY))+(PZ * sin(AngleY))
PX = NewX
PZ = NewZ
# Rotate Z
NewX = (PX * cos(AngleZ))-(PY * sin(AngleZ))
NewY = (PY * cos(AngleZ))+(PX * sin(AngleZ))
NewPoint.append(NewX)
NewPoint.append(NewY)
NewPoint.append(NewZ)
return NewPoint
class Renderer:
"""Render a scene viewed from a given camera.
This class is responsible of the rendering process, hence transormation
and projection of the ojects in the scene are invoked by the renderer.
The user can optionally provide a specific camera for the rendering, see
the #doRendering# method for more informations.
"""
def __init__(self):
"""Set the canvas size to a defaulr value.
The only instance attribute here is the canvas size, which can be
queryed to the renderer by other entities.
"""
self.canvasSize = (0.0, 0.0)
##
# Public Methods
#
def getCanvasSize(self):
"""Return the current canvas size read from Blender rendering context"""
return self.canvasSize
def doRendering(self, scene, cameraObj=None):
"""Control the rendering process.
Here we control the entire rendering process invoking the operation
needed to transforma project the 3D scene in two dimensions.
Parameters:
scene --- the Blender Scene to render
cameraObj --- the camera object to use for the viewing processing
"""
if cameraObj == None:
cameraObj = scene.getCurrentCamera()
context = scene.getRenderingContext()
self.canvasSize = (context.imageSizeX(), context.imageSizeY())
Objects = scene.getChildren()
# A structure to store the transformed scene
newscene = Scene.New("flat"+scene.name)
for obj in Objects:
if (obj.getType() != "Mesh"):
print "Type:", obj.getType(), "\tSorry, only mesh Object supported!"
continue
# Get a projector for this object
proj = Projector(cameraObj, obj, self.canvasSize)
# Let's store the transformed data
transformed_mesh = NMesh.New("flat"+obj.name)
transformed_mesh.hasVertexColours(1)
# Store the materials
materials = obj.getData().getMaterials()
meshfaces = obj.getData().faces
for face in meshfaces:
# if the face is visible flatten it on the "picture plane"
if self._isFaceVisible_old(face, obj, cameraObj):
# Store transformed face
newface = NMesh.Face()
for vert in face:
p = proj.doProjection(vert.co)
tmp_vert = NMesh.Vert(p[0], p[1], p[2])
# Add the vert to the mesh
transformed_mesh.verts.append(tmp_vert)
newface.v.append(tmp_vert)
# Per-face color calculation
# code taken mostly from the original vrm script
# TODO: understand the code and rewrite it clearly
ambient = -150
fakelight = Object.Get("Lamp").loc
if fakelight == None:
fakelight = [1.0, 1.0, -0.3]
norm = Vector(face.no)
vektori = (norm[0]*fakelight[0]+norm[1]*fakelight[1]+norm[2]*fakelight[2])
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)))
intensity = floor(ambient + 200*acos(vektori/vduzine))/200
if intensity < 0:
intensity = 0
if materials:
tmp_col = materials[face.mat].getRGBCol()
else:
tmp_col = [0.5, 0.5, 0.5]
tmp_col = [ (c>intensity) and int(round((c-intensity)*10)*25.5) for c in tmp_col ]
vcol = NMesh.Col(tmp_col[0], tmp_col[1], tmp_col[2])
newface.col = [vcol, vcol, vcol, 255]
transformed_mesh.addFace(newface)
# at the end of the loop on obj
transformed_obj = Object.New(obj.getType(), "flat"+obj.name)
transformed_obj.link(transformed_mesh)
transformed_obj.loc = obj.loc
newscene.link(transformed_obj)
return newscene
##
# Private Methods
#
def _isFaceVisible_old(self, face, obj, cameraObj):
"""Determine if the face is visible from the current camera.
The following code is taken basicly from the original vrm script.
"""
camera = cameraObj
numvert = len(face)
# backface culling
# translate and rotate according to the object matrix
# and then translate according to the camera position
#m = obj.getMatrix()
#m.transpose()
#a = m*Vector(face[0]) - Vector(cameraObj.loc)
#b = m*Vector(face[1]) - Vector(cameraObj.loc)
#c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
a = []
a.append(face[0][0])
a.append(face[0][1])
a.append(face[0][2])
a = RotatePoint(a[0], a[1], a[2], obj.RotX, obj.RotY, obj.RotZ)
a[0] += obj.LocX - camera.LocX
a[1] += obj.LocY - camera.LocY
a[2] += obj.LocZ - camera.LocZ
b = []
b.append(face[1][0])
b.append(face[1][1])
b.append(face[1][2])
b = RotatePoint(b[0], b[1], b[2], obj.RotX, obj.RotY, obj.RotZ)
b[0] += obj.LocX - camera.LocX
b[1] += obj.LocY - camera.LocY
b[2] += obj.LocZ - camera.LocZ
c = []
c.append(face[numvert-1][0])
c.append(face[numvert-1][1])
c.append(face[numvert-1][2])
c = RotatePoint(c[0], c[1], c[2], obj.RotX, obj.RotY, obj.RotZ)
c[0] += obj.LocX - camera.LocX
c[1] += obj.LocY - camera.LocY
c[2] += obj.LocZ - camera.LocZ
norm = [0, 0, 0]
norm[0] = (b[1] - a[1])*(c[2] - a[2]) - (c[1] - a[1])*(b[2] - a[2])
norm[1] = -((b[0] - a[0])*(c[2] - a[2]) - (c[0] - a[0])*(b[2] - a[2]))
norm[2] = (b[0] - a[0])*(c[1] - a[1]) - (c[0] - a[0])*(b[1] - a[1])
d = norm[0]*a[0] + norm[1]*a[1] + norm[2]*a[2]
#d = DotVecs(Vector(norm), Vector(a))
return (d<0)
def _isFaceVisible(self, face, obj, cameraObj):
"""Determine if the face is visible from the current camera.
The following code is taken basicly from the original vrm script.
"""
camera = cameraObj
numvert = len(face)
# backface culling
# translate and rotate according to the object matrix
# and then translate according to the camera position
m = obj.getMatrix()
m.transpose()
a = m*Vector(face[0]) - Vector(cameraObj.loc)
b = m*Vector(face[1]) - Vector(cameraObj.loc)
c = m*Vector(face[numvert-1]) - Vector(cameraObj.loc)
norm = m*Vector(face.no)
d = DotVecs(norm, a)
return (d<0)
def _doClipping(face):
return
# ---------------------------------------------------------------------
#
## Main Program
#
# ---------------------------------------------------------------------
# FIXME: really hackish code, just to test if the other parts work
def depthSorting(scene):
cameraObj = Scene.GetCurrent().getCurrentCamera()
Objects = scene.getChildren()
Objects.sort(lambda obj1, obj2:
cmp(Vector(Vector(cameraObj.loc) - Vector(obj1.loc)).length,
Vector(Vector(cameraObj.loc) - Vector(obj2.loc)).length
)
)
# hackish sorting of faces according to the max z value of a vertex
for o in Objects:
mesh = o.data
mesh.faces.sort(
lambda f1, f2:
# Sort faces according to the min z coordinate in a face
#cmp(min([v[2] for v in f1]), min([v[2] for v in f2])))
# Sort faces according to the max z coordinate in a face
cmp(max([v[2] for v in f1]), max([v[2] for v in f2])))
# Sort faces according to the avg z coordinate in a face
#cmp(sum([v[2] for v in f1])/len(f1), sum([v[2] for v in f2])/len(f2)))
mesh.faces.reverse()
mesh.update()
# update the scene
for o in scene.getChildren():
scene.unlink(o)
for o in Objects:
scene.link(o)
def vectorize(filename):
print "Filename: %s" % filename
scene = Scene.GetCurrent()
renderer = Renderer()
flatScene = renderer.doRendering(scene)
canvasSize = renderer.getCanvasSize()
depthSorting(flatScene)
writer = SVGVectorWriter(filename, canvasSize)
writer.printCanvas(flatScene)
Blender.Scene.unlink(flatScene)
del flatScene
# Here the main
if __name__ == "__main__":
try:
Blender.Window.FileSelector (vectorize, 'Save SVG', "proba.svg")
except:
vectorize("proba.svg")