Saturday, June 20, 2009

Blender tranform matrix and building scenes from primitive meshes

In this tutorial, we'll work through several concepts in Blender. The three key concepts covered are:
  • the use of the transform matrix (mesh.transform()) to orient and place a mesh in a scene,
  • the use of material properties to control how a mesh is rendered, and
  • the use of Blender Vector and Matrix classes.
This example was built to begin working towards the generation of graphs in Blender. One of the first steps in generating graphs is the ability to draw a line from one point to another. This can be done in many ways. Two ways to do this are the direct generation of meshes that form the graph or assemling primitives into a graph. This example works through the basic steps in locating and orienting primitive elements. Blender provides functions to generate some simple primitive meshes: tubes, cones, planes, grids, cubes, spheres, and the monkey face. When Blender creates these meshes they are placed on the origin in the scene. The put the meshes elsewhere, the meshes must be moved using setLocation() and setEuler() or by setting the transform matrix and using transform() function. In this example, the meshes are placed using the transform() function. The main work is done in the function lineMe(...) which creates a line from one point to another by creating a tube mesh, then rotating and translating the mesh to make it go between the two points. This function is defined in lines 38 through 111. To rotate the tube/cylinder, the ends of the line are used to determine the lenght of the tube. A tube is created equal to the length of the lines. The tube is rotated to the direction of the line by manually forming a rotation matrix which is placed in the tranform matrix in lines 79-111. This matrix is also configured to moved one end of the tube to one end of the line. With this function defined, we can easily place lines anywhere we want in the scene. So the example puts a red line in, then adds 3 lines, one for each axis. Finally, to form a grid to see where things are, the mesh primitive, grid is added. The mesh returned by the Mesh.Primitivies.Grid() initially appears as a solid plane. To make the mesh appear as a grid, the material attached to it is configured to only display the vertices and edges in the rendering. This is done in line 201. Also, since the appearance of a plane in the editor can get in the way, the grid is set to appear in the editor as a mesh in line 207. When this script is executed, it should generate the image shown at the top of this posting and save the image to your render path.
#!BPY

__doc__ = """
OrientationExample.py

Example demonstrating the following techniques in Blender 2.48:
1) Use of the transform matrix
2) Use of material properties to control how objects are rendered
3) Delete (unlink) and replacement of default objects in a scene
4) Use of cross products to generate a basis for the transform matrix
5) Using python to render an image
6) Using python to save the rendered image to disk
7) Use of Blender Vector and Matrix classes
8) Defining a function in Python

This script is executed at the command line by:
>blender -P OrientationExample.py
"""
__author__ = "edt"
__version__ = "1.0 2009/06/20"
__url__="Website, dataOrigami.blogspot.com"


##############################################################
# load the modules used in the script
import Blender
import bpy
from Blender import *
from Blender.Scene import Render
from Blender import Text
from Blender import Mathutils
from Blender.Mathutils import *
import math

##############################################################
# define function(s)

def lineMe(p1,p2,dia=0.1,verts=16):
 """
 This function returns a mesh which forms a line from point p1 to p2.
 The points can be passes as either blender vectors or lists of [x,y,z] points.
 This line is cylinder which goes from point p1 to p2.
 Optionally the diameter and number of vertices used to describe the line are passed.
 -------------------
 The line is formed by creating a cylinder with a length equal to the distance
 point p1 and p2. The line is then oriented using the transform matrix to rotate
 and translate the cylinder.
 """
 # use the class constructors from Blender to form vectors for p1 and p2
 p1 = Vector(p1)
 p2 = Vector(p2)

 # form a vector that points in the direction from p1 to p2
 dir = p2-p1          

 # get the length of the line we want that goes from p1 to p2
 length = dir.length

 # use Mesh.Primitives.Cylinder to create a mesh for the line
 me = Mesh.Primitives.Cylinder(verts,dia,length)

 ###############
 # in the next few steps, the direction vector is used to form a basis
 #      see http://en.wikipedia.org/wiki/Basis_(linear_algebra)
 # which allows us to create a transform matrix to rotate the cylinder
 # along the direction we want. The basic idea is that the vector from
 # p1 to p2 points in the direction we want. The cylinder created by
 # Mesh.Primitives.Cylinder is oriented along the z-axis. To rotate the
 # cylinder, we  # rotate the z-axis in this direction. To completely specify
 # how to rotate, we need to provide information on how to rotate the x and y axes.
 # To define this, a matrix which is orthonormal (see http://en.wikipedia.org/wiki/Orthonormal)
 # is created from the direction vector. To create the other vectors in the
 # orthonormal basis, cross products are used to find orthogonal vectors.
 #
 # use the normalize function to set the length of the direction vector to 1
 dir.normalize()
 u = dir
 uu = Vector([0,0,1.0])
 if abs(AngleBetweenVecs(u,uu))>1e-6:
  # the direction of the line is different
  # from the z-axis
  # find the orthonormal basis
  v = CrossVecs(u,uu)
  w = CrossVecs(u,v)
  # form the transform matrix:
  #   > The first 3 rows and 3 columns form
  #   a rotation matrix because the any vertex transformed by this
  #   matrix will be the same distance from the origin as the original
  #   vertex. If this property is not preserved, then any shape formed
  #   will be skewed and scaled by the transform.
  #   > The first 3 columns in the last row define the translation
  #   applied to any vertex. In this function, the translation move the
  #   moves the end of the cylinder to the origin, then moves the end
  #   to p1.
  A = Matrix(
    [w[0],w[1],w[2],0],
    [v[0],v[1],v[2],0],
    [u[0],u[1],u[2],0],
    [dir[0]/2.0*length+p1[0],dir[1]/2.0*length+p1[1],dir[2]/2.0*length+p1[2],1])
 else:
  # the direction of the line is parallel to the z-axis
  # see the notes above on how the matrix is formed.
  A = Matrix(
    [1,0,0,0],
    [0,1,0,0],
    [0,0,1,0],
    [dir[0]/2.0*length+p1[0],dir[1]/2.0*length+p1[1],dir[2]/2.0*length+p1[2],1])

 # apply the transform to the cylinder
 me.transform(A,True)
 return me

##############################################################
# Get rid of the lamp and cube from the default scene
scene = Scene.GetCurrent()
for ob in scene.objects:
   print ob.getName()
   if ((cmp(ob.getName(),'Lamp')==0)
       (cmp(ob.getName(),'Cube')==0)
    (cmp(ob.getName(),'Camera')==0)):
     scene.objects.unlink(ob)
##############################################################
# add a camera and set it up
#
camdata = Camera.New()
cam = scene.objects.new(camdata)
# use setLocation to control the position of the camera
cam.setLocation(2.481,-1.508,1.1)
# use set Euler to control the angle of the camera
cam.setEuler(73.559*(3.1415/180),0.620*(3.1415/180),46.692*(3.1415/180))
scene.objects.camera = cam
##############################################################
# add a lamp and set it up
#
lampdata = Lamp.New()
lampdata.setEnergy(1.0)
lampdata.setType('Hemi')
lamp = scene.objects.new(lampdata)
lamp.setLocation(-5.0,-6.0,5.0)
lamp.setEuler(0.0,0.0,0.0)
##############################################################
# create the objects in the scene and bind materials to them
#

# define the ends of the line
p1 = Vector([0.2,0.3,-0.1])
p2 = Vector([0.5,1.0,1.0])
# form a mesh for th eline
me = lineMe(p1,p2)
# add the line to the scene
ob = scene.objects.new(me,'lineOb')
# bind a red material to the line
mat = Material.New('redMat') # create a new Material called 'redMat'
mat.rgbCol = [0.8, 0.1, 0.5] # change the color of the line
#
# attach the material to the mesh
#   note 1) a different technique is used for meshes
#       compared to how materials are attached to objects
#   note 2) we can attach the material to the mesh even after
#       the mesh was assigned to the object. This happens
#       because the mesh and the object really are pointers
#       to the same information in memory.
me.materials += [mat]
#
##############################
#
# create the lines for axes and a ball at the origin
p0 = Vector([0,0,0])
px = Vector([1,0,0])
py = Vector([0,1,0])
pz = Vector([0,0,1])
xMe = lineMe(p0,px)
yMe = lineMe(p0,py)
zMe = lineMe(p0,pz)
xOb = scene.objects.new(xMe,'xAxisOb')
yOb = scene.objects.new(yMe,'yAxisOb')
zOb = scene.objects.new(zMe,'zAxisOb')
originMe = Mesh.Primitives.UVsphere(32,32,0.1)
originOb = scene.objects.new(originMe,'originOb')

mat = Material.New('axisMat') # create a new Material called 'newMat'
mat.rgbCol = [1.0, 1.0, 0.0] # change its color

# attach the materials using two different techniques
#  >for the xaxis and the origin, the material is attached to the mesh by
#  by referenging the mesh from the object using
#  getData
#  > for the y & z-axis, we use the mesh reference. Since the
#  objects in the scene just point to the original mesh
#  attached to them, changing the mesh changes the object.
originOb.getData(False,True).materials+=[mat]
xOb.getData(False,True).materials += [mat]
yMe.materials += [mat]
zMe.materials += [mat]

# create a grid for the axes and place it on the x-y plane with
# use material properties to make it look like a grid
#  by only drawing vertices and edges
mat = Material.New('axisGridMat') # create a new Material called 'newMat'
mat.rgbCol = [1.0, 1.0, 0.0]      # change its color
mat.mode = Material.Modes.WIRE   # only render edges and vertices
zGridMe = Mesh.Primitives.Grid(10,10,1)
zGridOb = scene.objects.new(zGridMe,'zGridOb')
zGridOb.setLocation(0.5,0.5,0)
zGridOb.getData(False,True).materials += [mat]
# this makes the grid appear as an outline in the Blender editor
zGridOb.setDrawType(Object.DrawTypes["WIRE"])
#######################################
# render the image and save the image
#
context = scene.getRenderingContext()
# enable seperate window for rendering
Render.EnableDispWin()
context.imageType = Render.JPEG
# draw the image
context.render()
# save the image to disk
# to the location specified by RenderPath
# by default this will be a jpg file
context.saveRenderedImage('LineOrientationExample.jpg')
Window.RedrawAll()
#
########################################


To copy the code snippets easily, see:http://dataorigami.blogspot.com/2009/06/how-to-easily-copy-code-snippets-from.html

To execute the script in Blender, see:http://dataorigami.blogspot.com/2009/06/how-to-execute-script-from-command-line.html

To work with the script in a simple IDE, see:http://dataorigami.blogspot.com/2009/04/developing-scripts-for-blender.html

1 comment:

  1. Hi,
    The deletion of the Cube etc seems to be wrong (errormessage! Blender 2.49)

    Otherwise perfect!

    ReplyDelete