Thursday, May 21, 2009

Using Python and Blender Game Engine to build an interactive visualization

The purpose of this example is to demonstrate how to use Python and the Blender Game Engine (BGE) to create an interactive visualization. When done, the blender model will allow you to rotate and move a polyhedron (pyramid) using the arrow keys when the game engine is running. I'm assuming you have anough knowledge that I do not need to walk you through every step in the Blender environment. As far as I can tell, Blender 2.48 does not support programmatic definition of BGE sensors, controllers, and actuators. Therefore, to use Python to define an interactive visualization in Blender, we need to start with a Blender file that provides everything needed that Python can not add. This seed model consists of an text object, sensors, and a controller. The script that builds the visualization then loads this model and modifies it. To build the seed model start by opening Blender. You should see a screen similar to this: So it is easy to see what is going on, split the screen. On the left hand screen, change to the text editor screen. Click on that screen. Now open a new text block using either alt-N or File>New. Name this new text block 'MainScript.py'. When you are done you should see a screen similar to this: The next step is to add the sensors and a controller to the scene. Click in the bottom display window. Press F4 or select the Logic button. This panel will allow you to add sensors and a controller. After pressing F4 you should see the following: Make the camera visible in the logic panel. This can be done by pressing the 'a' key twice. This deselects everything, then selects everything. Once this is done, the logic panel should look like this: To add the sensors, in the sensors column, press the add button next to the camera. This will cause a form for a sensor to appear. Add 5 sensors. The five sensors should be configured as:
  • 'always' named 'sAlways',
  • 'keyboard' named 'sKeyboard' with 'all' selected,
  • 'mouse' named 'sMouseRight' with 'right button' selected,
  • 'mouse' named 'sMouseLeft' with 'left button' selected,
  • 'mouse' named 'sMouse' with 'movement selected'
The always sensor will trigger a script on every frame when the Blender Game Engine (BGE) runs. The keyboard sensor will trigger a script when a key is pressed or released. The mouse sensors will trigger and event when the mouse buttons are pressed or the mouse moves. The names are added so it is easy to reference the sensors in the BGE script. Once done, you should see the following: Next, a controller is added to the camera object. Press the add button beside the camera button in the controller column. Configure the controller as a 'Python' controller. Specify the script as 'MainScript.py'. This causes the script stored in the text block 'MainScript.py' to be executed on a trigger. We will load the script into this block using the script we will write later. Finally, connect all of the sensors to the controller by clicking on the circle to the right of each sensor then clicking on the circle to the left of the controller. When you are done, it should look like this: Save this blender file as 'bgeExample.blend'. To simplify things, save the files into the 'tmp' directory. If this directory does not exist, create it. At this point, we have a template that can be modified by a Python script. The next step is to build that script. To simplify things, copy the following code into your favorite editor and save. My preference is to work with Notepad++. See these instructions to execute Blender from inside Notepad++. To see everything work, execute this script, then when Blender opens, press the 'P' key. This will start the game engine and allow you to move/rotate the polygon using the arrow and shift-arrow keys. You should see the following once the game engine starts: To use Python to create the visualization ...
#!BPY
"""
Name: 'HowToBuildAnInteractiveBGEScene'
Blender: 248
Group: ''
"""
##########################################################3
import Blender
import bpy
from Blender import * 
from Blender.Scene import Render 
from Blender import Text

##############################################################
# load the template blender file
Blender.Load('c:\\tmp\\bgeExample.blend')

##############################################################
# reload the modules because a load resets the Python interpreter
import Blender
import bpy
from Blender import * 
from Blender.Scene import Render 
from Blender import Text


##############################################################
# name for the embedded script in the loaded file
gameScriptName = 'MainScript.py'

##############################################################
# Get rid of the lamp and cube from the default scene
#     - leave the camera, it has the linkages for the scripting
#       for the BGE.
#     - Note: Do NOT make any object the BGE scripts invisible
#       via ob.layers=[], this will disable the scripts
#       attached to the object.
scene = Scene.GetCurrent() 
for ob in scene.objects:
   print ob.getName()
   if ((cmp(ob.getName(),'Lamp')==0) |
    (cmp(ob.getName(),'Cube')==0)):
  scene.objects.unlink(ob)
##############################################################
# reuse the camera in the default scene
##############################################################
# add a lamp and set it up
#
lampdata = Lamp.New() 
lampdata.setEnergy(10.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 mesh and bind a material to it
#

coords=[ [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1], [0,0,1] ]  
faces= [ [3,2,1,0], [0,1,4], [1,2,4], [2,3,4], [3,0,4] ]

me = bpy.data.meshes.new('myMesh')
me.verts.extend(coords)
me.faces.extend(faces)
ob = scene.objects.new(me,'myObj')
ob.setEuler(0.2,0.2,0.1)
# 
mat = Material.New('newMat') # create a new Material called 'newMat' 
mat.rgbCol = [0.8, 0.2, 0.2] # change its color 
# 
# 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]
# 
Window.RedrawAll() 
#
#######################################
# setup the BGE handler to animate the polygon
#   based on keyboard inputs
#
# create the text that handles the event
gameScript = """
#######################################
# tasks for each iteration thru this script
# 1) Output diagnostic information to console
# 2) rotate or move myObj based on keyboard input
#      - the arrow keys move the object up/dn, rt/left
#      - shift + arrows key rotates the object

print 'BGE script executing...'

import Blender
print Blender.sys.time()

# GameLogic has been added to the global namespace no need to import

# for keyboard event comparison
import GameKeys 

# support for Vector(), Matrix() types and advanced functions like AngleBetweenVecs(v1,v2) and RotationMatrix(...)
import Mathutils 

# for functions like getWindowWidth(), getWindowHeight()
import Rasterizer

# for matrix operations
import Mathutils

def main():
 cont = GameLogic.getCurrentController()
 sce = GameLogic.getCurrentScene()
 ob   = sce.getObjectList()['OBmyObj']
 sens = cont.getSensor('sKeyboard') 
 if sens.isPositive():
  print '--key pressed--'
  fullKeyList = sens.getCurrentlyPressedKeys()
  keyList=[]
  for key in fullKeyList:
   keyList.append(key[0])
  pos = ob.getPosition()
  orientL = ob.getOrientation()
  orientM = Mathutils.Matrix(orientL[0],orientL[1],orientL[2])
  orientM.transpose()
  orient  = orientM.toEuler()
  if (GameKeys.RIGHTSHIFTKEY in keyList) or (GameKeys.LEFTSHIFTKEY in keyList):
   print '--Shift Pressed--'
   if GameKeys.RIGHTARROWKEY in keyList:
    orient[1]=orient[1]+0.5
    print '--Right Arrow Key--'
   elif GameKeys.LEFTARROWKEY in keyList:
    orient[1]=orient[1]-0.5
    print '--Left Arrow Key--'
   if GameKeys.UPARROWKEY in keyList:
    orient[0]=orient[0]+0.5
    print '--Up Arrow Key--'
   elif GameKeys.DOWNARROWKEY in keyList:
    orient[0]=orient[0]-0.5
    print '--Down Arrow Key--'  
   else:
    print '--other key--'
  else:
   if GameKeys.RIGHTARROWKEY in keyList:
    pos[0]=pos[0]+0.1
    print '--Right Arrow Key--'
   elif GameKeys.LEFTARROWKEY in keyList:
    pos[0]=pos[0]-0.1
    print '--Left Arrow Key--'
   if GameKeys.UPARROWKEY in keyList:
    pos[1]=pos[1]+0.1
    print '--Up Arrow Key--'
   elif GameKeys.DOWNARROWKEY in keyList:
    pos[1]=pos[1]-0.1
    print '--Down Arrow Key--'  
   else:
    print '--other key--'
  orientM = orient.toMatrix()
  orientM.transpose()
  ob.setOrientation(orientM)
  ob.setPosition(pos)  

main()  
"""
txt = Text.Get(gameScriptName) #get the gamescript
txt.clear()                    # clear the existing script
txt.write(gameScript)          # appending text

#######################################
#  Save the blender file
# 
Blender.Save('C:\\tmp\\FullBGEExample.blend',1)
This script deletes the default lamp and cube, then adds a new lamp and polygon. The default camera is kept since it has the links for the game engine attached to it. The script adds the game engine script to the 'MainScript.py' text block. Once the script executes, Blender should open and look like this: To see everything work, execute this script, then when Blender opens, press the 'P' key. This will start the game engine and allow you to move/rotate the polygon using the arrow and shift-arrow keys. You should see the following once the game engine starts:
New code formatting: Thanks to techqi and Alex Gorbatchev.

Sunday, May 17, 2009

How to generate an animation using Python in Blender

This script takes the default scene in Blender, removes the camera, lamp, and cube, then adds in a new camera, lamp, and polytope. On each frame in the animation, the polytope is rotated. The script generates an AVI file of the animation. Additionally, this script shows one way to unlink or delete an object from a scene. It also shows how to use python to add a script to a text object, then link that script to an event in Blender. Note: I could not get this script to work when creating a scene from scratch. There appears to be an flag that needs to be set to enable multiple frames to be generated using a Python script. If anyone knows of an example that demonstrates how to do this from scratch, please let me know.
import Blender import bpy from Blender import * from Blender.Scene import Render from Blender import Text ############################################################## # Clear default scene # - could not get animation to work when starting from new scene # scene = Scene.GetCurrent() for ob in scene.objects: print ob.getName() if ((cmp(ob.getName(),'Cube')==0) | (cmp(ob.getName(),'Camera')==0) | (cmp(ob.getName(),'Lamp')==0)): scene.objects.unlink(ob) ############################################################## # add a camera and set it up # camdata = Camera.New() cam = scene.objects.new(camdata) cam.setName('Camera01') cam.setLocation(-2.0,-7.0,1.0) cam.setEuler(1.5,0.0,-0.15) ############################################################## # add a lamp and set it up # lampdata = Lamp.New() lampdata.setEnergy(10.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 mesh and bind a material to it # coords = [ [-1,-1,-1],[-1,-1,1],[-1,1,-1],[-1,1,1], [ 1,-1,-1],[ 1,-1,1],[ 1,1,-1],[ 1,1,1]] faces = [ [0,1,3],[0,1,5],[0,3,5],[1,3,5] ] me = bpy.data.meshes.new('myMesh') me.verts.extend(coords) me.faces.extend(faces) ob = scene.objects.new(me,'myObj') ob.setEuler(0.2,0.2,0.1) # mat = Material.New('newMat') # create a new Material called 'newMat' mat.rgbCol = [0.8, 0.2, 0.2] # change its color # # 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] # Window.RedrawAll() # ####################################### # setup the event handler to animate the polygon # # create the text that handles the event animationScript = """ # get the polygon from the scene print 'linked script started' import Blender frame=Blender.Get('curframe') sc =Blender.Scene.GetCurrent() ob =Blender.Object.Get('myObj') ob.setEuler(0.2+0.02*frame,0.2+0.005*frame,0.1+0.005*frame) print 'linked script completed - Frame %d' % frame #obs = sc.getChildren() #for ob in obs # if ob.getName=='myObj': # ob.setEuler(0.2+0.002*frame,0.2,0.1) """ EVENT = "FrameChanged" handlerName = 'rotateMyObject' txt = Text.New(handlerName) # create a new Text object txt.write(animationScript) # appending text scene.addScriptLink(handlerName,EVENT) ####################################### # render the image and save the image # scene.setName('AnimationScene') context = scene.getRenderingContext() context.displayMode=1 context.sizePreset(Render.PC) context.setImageType(Render.AVIRAW) context.sFrame = 1 context.eFrame = 10 context.fps=4 context.setRenderPath('\\tmp\\animTest') context.renderAnim() ####################################### # Save the blender file # Blender.Save('C:\\tmp\\AnimExample.blend')

Tuesday, May 5, 2009

How to create a Polyhedron in Blender using Python

In this example, a simple 4 sided polygon is generated using meshes. The bulk of the example is dedicated to setting up the cameras, lighting, and rendering the image. The mesh is generated and linked to the scene in the following lines:
coords = [ [-1,-1,-1],[-1,-1,1],[-1,1,-1],[-1,1,1], [ 1,-1,-1],[ 1,-1,1],[ 1,1,-1],[ 1,1,1]] faces = [ [0,1,3],[0,1,5],[0,3,5],[1,3,5] ] me = bpy.data.meshes.new('myMesh') me.verts.extend(coords) me.faces.extend(faces) ob = scene.objects.new(me,'myObj') ob.setEuler(0.2,0.2,0.1)
The material for the mesh is created in the following code:
mat = Material.New('newMat') # create a new Material called 'newMat' mat.rgbCol = [0.8, 0.2, 0.2] # change its color
Once the material is created, it is linked to the mesh (not the object linking to the mesh) using this line:
me.materials += [mat]
A key point to notice is that this approach to linking materials is different than the technique used with objects. In fact, the following line will NOT work.
# This does not work as expected! ob.setMaterials([mat])
Here is a complete listing for the example:
#!BPY """ Name: 'HowToAddMesh002' Blender: 248 Group: 'Examples' """ ##########################################################3 import Blender import bpy from Blender import * from Blender.Scene import Render ############################################################## # Initialize a new scene # scene = Scene.New() # make this scene the active scene in the screen scene.makeCurrent() ############################################################## # add a camera and set it up # camdata = Camera.New() cam = scene.objects.new(camdata) cam.setLocation(-2.0,-7.0,1.0) cam.setEuler(1.5,0.0,-0.15) ############################################################## # add a lamp and set it up # lampdata = Lamp.New() lampdata.setEnergy(10.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 mesh and bind a material to it # coords = [ [-1,-1,-1],[-1,-1,1],[-1,1,-1],[-1,1,1], [ 1,-1,-1],[ 1,-1,1],[ 1,1,-1],[ 1,1,1]] faces = [ [0,1,3],[0,1,5],[0,3,5],[1,3,5] ] me = bpy.data.meshes.new('myMesh') me.verts.extend(coords) me.faces.extend(faces) ob = scene.objects.new(me,'myObj') ob.setEuler(0.2,0.2,0.1) # mat = Material.New('newMat') # create a new Material called 'newMat' mat.rgbCol = [0.8, 0.2, 0.2] # change its color # # 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] # Window.RedrawAll() # ####################################### # 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('MeshExample002.jpg') ####################################### # Save the blender file # Blender.Save('MeshExample002.blend')

Monday, May 4, 2009

An observation ...

Industrial research is the fine art of spending small amounts of money today to prevent spending large amounts later.

How to add a mesh to Blender using Python

There are some useful examples in the API reference on the Mesh class. Here is a complete example that illustrates a minimal number of statements required to add a single triangular mesh.
#!BPY """ Name: 'HowToAddMesh001' Blender: 248 Group: 'Examples' """ ##########################################################3 import Blender import bpy from Blender import * from Blender.Scene import Render ############################################################## # Initialize a new scene # scene = Scene.New() # make this scene the active scene in the screen scene.makeCurrent() ############################################################## # add a camera and set it up # camdata = Camera.New() cam = scene.objects.new(camdata) cam.setLocation(0.0,-7.0,1.0) cam.setEuler(1.2,0.0,-0.15) ############################################################## # add a lamp and set it up # lampdata = Lamp.New() lampdata.setEnergy(10.0) lampdata.setType('Hemi') lamp = scene.objects.new(lampdata) lamp.setLocation(0.0,-1,5) lamp.setEuler(0.0,0.0,0.0) ############################################################## # create the mesh and bind a material to it # coords = [ [-1,-1,-1],[1,-1,-1],[1,1,-1]] faces = [ [2,1,0]] me = bpy.data.meshes.new('myMesh') me.verts.extend(coords) me.faces.extend(faces) ob = scene.objects.new(me,'myObj') # mat = Material.New('newMat') # create a new Material called 'newMat' mat.rgbCol = [0.8, 0.2, 0.2] # change its color # ob.setMaterials([mat]) # Window.RedrawAll() # ####################################### # render the image and save the image # #scn = Scene.GetCurrent() context = scene.getRenderingContext() # enable seperate window for rendering Render.EnableDispWin() # 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('MeshExample001') ####################################### # Save the blender file # Blender.Save('MeshExample001.blend')