Sunday, August 23, 2009

Release of Data Origami Library 0.006

The Data Origami Library for Blender 2.49a has been updated. This library's goal is to reduce the number of lines of python required to generate a useful image, animation, or game in Blender.

In this release, the image class now supports the following methods:

  • Orienting the camera by using a lookAt and lookFrom location
  • Adding Blocks defined by opposing corners
  • Adding Cylinders defined by end locations
  • Adding Spheres
  • Adding Text boxes
  • Adding Lighting which can be aimed at a target
  • Adding Tubes which can be used to draw arbitrary curves from either piecewise lines or smooth curves.

The library can be downloaded here. To install, simply unzip to a working directory and execute the test scripts from the Blender command line.

The images above were generated by the test scripts contained in the zip files.

This example shows how the library can be used to generate an image with just a few lines of python.

#BPY
# setup to allow the module to be imported from the same directory
#   the script is in
import sys
import os
filename = sys.argv[2]
filepath = os.path.dirname(filename)
sys.path.insert(0,filepath)
import OLib as MyLib

import Blender
import Blender.Mathutils
from Blender.Mathutils import *
from Blender import *

image = MyLib.Image('MyScene')
#image.lookFrom(3.5,3,15)
image.lookFrom(15,5,15)
image.lookAt(0,0,1)

ob = image.addPolyline()
image.addTube(tubeShape=ob)
image.addCylinder(p1=[0,0,0],p2=[0,0,5])

image.save(filepath+'\\images\\'+'test001_Tube.jpg')

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

Sunday, August 16, 2009

Script to find the type of each object in a Blender scene

This examples applies to Blender 2.49 and some earlier versions.
import Blender
from Blender import *
scn = Scene.GetCurrent()
obs = scn.objects
for ob in obs:
    print 'Object named %s has type %s' % (ob.name,ob.type)

Monday, August 10, 2009

Portable Blender and Notepad++

One of the problems I have in my system is that I use PythonXY. PythonXY works best when it is the only registered Python on a system. The problem arises when installing Blender and PythonXY and both require different versions of Python. Blender Portable offers a way to solve this problem. Blender Portable installs a local copy of Python which can be different from any other Python installations on a machine. This portable version of Blender can still be used with Notepad++. To setup Notepad++, follow the instructions here, but use the following string to call Blender.
"C:\PortableApps\BlenderPortable\blenderportable.exe" -P "$(FULL_CURRENT_PATH)"

Release of Data Origami Library 0.002

The Data Origami Library has been updated. The image class now supports the following objects:
  • Orienting the camera by using a lookAt and lookFrom location
  • Blocks defined by opposing corners
  • Cylinders defined by end locations
  • Spheres
  • Text boxes

An issue with shading was corrected. The library now generates shadows correctly.

The library can be downloaded here.

The image above was generated by the test script here.

Thursday, August 6, 2009

Interesting Dataset: BLS Time Use Survey

This is an interesting dataset which explains how people use their time in the US: BLS Time Use Survey.

Sunday, August 2, 2009

The 0.001 Release of the Data Origami Library for Blender

This is a shell that will be expended in the future. It simplifies the problem of setting up an image in Blender. Other postings will build examples on top of this module.
For now, it is recommended to place this file in C:\tmp. I'll use that location in example scripts. A future release will have a more permanent recommended home.
Code: Download Code for the Origami Library 0.001. This file can be placed arbitrarily. Any script which imports this module must reference it by the full path because of how Blender handles files.
#!BPY
__doc__ = """
OLib_xxxx.py
Rev 0.001
This module setups up a scene for static rendering, animation or game engine operation
This module provides useful library functions.

This module does nothing from the Blender command line

------------------
History:
rev 0.001 8/2/2009
   Initial release to test structure and functionality
 edt 
"""

__author__ = "edt"
__version__ = "0.001 2009/08/03"
__url__="Website, dataOrigami.blogspot.com"
##############################################################

__doc__ = """
This module setups up a scene for static rendering, animation or game engine operation
This module imports useful library functions.

rev 0.001
   Initial release to test structure and functionality
 edt 8/2/2009
"""

 
import Blender
import Blender.Mathutils
from Blender.Mathutils import *

class Image(object):
 def __init__(self,name='Scene'):
  # create new scene
  scene = Blender.Scene.New(name)
  scene.makeCurrent()
  ##############################################################
  # add a camera and set it up
  #     
  camdata = Blender.Camera.New() 
  cam = scene.objects.new(camdata) 
  # use setLocation to control the position of the camera
  scene.objects.camera = cam
  self.cam   = cam
  self._lookAt_x = 0
  self._lookAt_y = 0
  self._lookAt_z = 0
  self.lookFrom(2.481,-1.508,1.1)
  ##############################################################
  # add a lamp and set it up
  #
  lampData = Blender.Lamp.New() 
  lampData.setEnergy(1.0) 
  lampData.setType('Lamp')
  lampData.mode |= Blender.Lamp.Modes["RayShadow"]  # make shadows appear
  lamp = scene.objects.new(lampData) 
  lamp.setLocation(2.0,2.0,5.0) 
  lamp.setEuler(120*(3.1415/180),30*(3.1415/180),-30*(3.1415/180))
  #
  ################################################################
  # setup world
  world = Blender.World.New(name+'_World')
  world.setCurrent()
  
  
  self.scene = scene
  self.lamp  = lamp
  self.world = world
 
 def lookFrom(self,*args):
  if len(args)==1:
   # we have an object which contains look information
   x = args[0].x
   y = args[0].y
   z = args[0].z
  elif len(args)==3:
   # we have x,y,z coordinates
   x = args[0]
   y = args[1]
   z = args[2]
  # set camera position to x,y,z 
  self.cam.setLocation(x,y,z)
  self.lookAt()
 
 def lookAt(self,*args):
  if len(args)==0:
   to_x = self._lookAt_x
   to_y = self._lookAt_y
   to_z = self._lookAt_z
   up_x = 0
   up_y = 1
   up_z = 0
  elif len(args)==1:
   # we have an object which contains look information
   to_x = args[0].x
   to_y = args[0].y
   to_z = args[0].z
   up_x = 0
   up_y = 1
   up_z = 0
  elif len(args)==2:
   # we have an object which contains look information
   to_x = args[0].x
   to_y = args[0].y
   to_z = args[0].z
   up_x = args[1].x
   up_y = args[1].y
   up_z = args[1].z
  elif len(args)==3:
   # we have x,y,z coordinates
   to_x = args[0]
   to_y = args[1]
   to_z = args[2]
   up_x = 0
   up_y = 1
   up_z = 0
  elif len(args)==6:
   # we have x,y,z coordinates
   to_x = args[0]
   to_y = args[1]
   to_z = args[2]
   up_x = args[3]
   up_y = args[4]
   up_z = args[5]
  # save view target
  self._lookAt_x = to_x
  self._lookAt_y = to_y
  self._lookAt_z = to_z
  # set camera position to x,y,z 
  from_x, from_y, from_z = self.cam.getLocation()
  p1 = Vector([from_x,from_y,from_z])
  p2 = Vector([to_x,to_y,to_z])
  
  # form a vector that points in the direction from p1 to p2
  dir = p2-p1                  
  #print 'from='+str(p1)
  #print 'to='+str(p2)
  #print 'dir='+str(dir)
  
  dir.normalize()
  #u = dir
  up = Vector([up_x,up_y,up_z])
  up.normalize()
  dir_ref = Vector([0,0,-1.0])   # direction the camera naturally points w/o rotation
  if True: #abs(AngleBetweenVecs(dir,dir_ref))>1e-6:
   d = -dir
   e = up-up*DotVecs(dir,up)
   e.normalize()
   f = -CrossVecs(d,e)
   A = Matrix(
     [f[0],f[1],f[2],0],
     [e[0],e[1],e[2],0],
     [d[0],d[1],d[2],0],
     [from_x,from_y,from_z,1])
  else:
   # the direction of view is parallel to the z-axis
   A = Matrix(
     [1,0,0,0],
     [0,1,0,0],
     [0,0,1,0],
     [from_x,from_y,from_z,1]) 
  # apply the transform to the camera   
  self.cam.setMatrix(A)

 def add(self,item,name='noName'):
  # this method allows any object to be added to the scene
  ob = self.scene.objects.new(item,name)
  return ob
  
 def delete(self,item):
  self.scene.objects.unlink(item)
  
 def addSphere(self,**kwargs):
  # default the values
  diameter = kwargs.get('diameter',1.0)
  segments = kwargs.get('segments',8)
  rings = kwargs.get('rings',8)
  loc   = kwargs.get('location',[0,0,0])
  useIco = kwargs.get('useIco',False)
  useUV = kwargs.get('useUV',True)
  subdivisions = kwargs.get('subdivisions',2)
  name = kwargs.get('name','sphere')
  if useIco:
   sphere = Blender.Mesh.Primitives.Icosphere(subdivisions,diameter)
  else: 
   sphere = Blender.Mesh.Primitives.UVsphere(segments,rings,diameter)
  ob = self.add(sphere,name)  
  ob.setLocation(loc)
  return ob
  
 def addCube(self,*kwargs):
  pass
  
 def addCylinder(self,**kwargs):
  pass
  
 def addPlane(self,**kwargs):
  pass
 
 def addText(self,**kwargs):
  loc   = kwargs.get('location',[0,0,0])
  name  = kwargs.get('name','sphere')
  txtStr= kwargs.get('text','Default Message')
  width = kwargs.get('width',1.0)
  height= kwargs.get('height',1.0)
  col   = kwargs.get('color',[1.0,1.0,1.0])
  align = kwargs.get('align','LEFT')
  txt = Blender.Text3d.New()
  txt.setText(txtStr)
  txt.setSize(0.1)
  alignDict = {'LEFT':Blender.Text3d.LEFT,'RIGHT':Blender.Text3d.RIGHT,
      'MIDDLE':Blender.Text3d.MIDDLE, 'FLUSH':Blender.Text3d.FLUSH}
  txt.setAlignment(alignDict[align])
  mat=Blender.Material.New('textMat')
  mat.rgbCol = col
  ob = self.add(txt)
  me = Blender.Mesh.New('textMesh')
  me.getFromObject(ob)
  me.materials += [mat]
  self.delete(ob)
  newOb = self.add(me,name)
  
  # force a redraw to ensure that the bounding box is updated!!
  Blender.Window.RedrawAll() 
  boundBox = newOb.getBoundBox(1)
  upperBox = max(boundBox)
  lowerBox = min(boundBox)
  initialWidth = upperBox[0]-lowerBox[0]
  initialHeight = upperBox[1]-lowerBox[1]
  widthRatio = width/initialWidth
  heightRatio = height/initialHeight
  ratio = min(widthRatio,heightRatio)
  
  newOb.setSize(ratio,ratio,ratio)
  newOb.setLocation(loc)
  return newOb


 
 def addWorld(self,**kwargs):
  pass
 
 def drawNow(self,**kwargs):
  xSize = kwargs.get('xSize',1024)
  ySize = kwargs.get('ySize',800)
  overSampleLevel = kwargs.get('oversamplingLevel',16)
  #print 'Image.drawNow() executing'
  self.context = self.scene.getRenderingContext() 
  # enable seperate window for rendering 
  Blender.Scene.Render.EnableDispWin() 
  self.context.imageType = Blender.Scene.Render.JPEG
  if overSampleLevel>0:
   self.context.enableOversampling(True)
   self.context.setOversamplingLevel(overSampleLevel)
  else:
   self.context.enableOversampling(False)   
  self.context.imageSizeX(xSize)
  self.context.imageSizeY(ySize)
  # draw the image 
  self.context.render()   
 
 def save(self,filename='image.jpg'):
  self.drawNow()
  self.context.saveRenderedImage(filename) 

class Animation(object):
 pass
 
class BEG(object):
 pass


# This does not work with Blender
#if name=='__main__':
# # test/demo codes here
# pass
 

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

Using Blender Subsurfaces from Python

In Blender, one of the ways to improve the smoothness of an object is to use subsurfaces. Programatically, this is done by changing the modifiers attached to an object. In this example, a UVsphere is modified by changing the number of rings and segments. The same basic UVsphere is also modified by adding subsurface levels. From looking at the table, it is possible to see how increasing subsurface level and rings/segments both get a smooth looking sphere.

To get this example to run, you will need to copy the Data Origami Library rev 0.001 to C:\tmp (or modify the path to point to your location of choice).

Code:
Download Code to draw the spheres with different rings, segments and subsurface levels.

#BPY
__doc__ = """
Sphere Subsurface Example
Rev 1.0
This script demonstrates the effect of subsurface. 
This script depends on the Origami Library and expects it to be located at C:\tmp.

This script is executed at the command line by:>blender -P subsurf_ex_XXXX.py"""

__author__ = "edt"
__version__ = "1.0 2009/08/03"
__url__="Website, dataOrigami.blogspot.com"

##############################################################


import sys
# make sure a copy of OLib_001.py is at C:\tmp
# add to the system path to point to the file to import
sys.path.append("c:\\tmp")

import OLib_001 as MyLib
import Blender
import Blender.Mathutils
from Blender.Mathutils import *
from Blender import *

image = MyLib.Image('MyScene')
image.lookFrom(3.5,3,15)
image.lookAt(3.5,3,0)

image.addText(text='UVSpheres with SubSurfaces',width=5,height=1,
   location=[5,6.5,0],align='MIDDLE')

xList = range(0,10)
ringList = [3,4,5,6,7,8,9,10,11,12]
segmentList = ringList

image.addText(text='Num Rings =',width=10,height=0.4,
   location=[-0.25,-0.75,0],align='RIGHT',
   color=[1.0,0.0,0.0])
ob=image.addText(text='Num SubSurfaces', width=20,height=0.4,
            location=[-1,3,0],align='MIDDLE',
   color=[0.0,1.0,0.0])
ob.setEuler(0,0,90*3.1415/180.0)
for i in range(0,len(ringList)):
 rings = ringList[i]
 segments = segmentList[i]
 image.addText(text=str(rings),width=1,height=0.4,
    location=[xList[i],-0.75,0],align='MIDDLE',
    color=[1.0,0.0,0.0])
 for subSurfLevel in range(0,7):
  # add a UVsphere
  UVSphereOb = image.addSphere(diameter=0.5,rings=rings, 
   segments=segments,location=[xList[i],subSurfLevel,0])
  mods = UVSphereOb.modifiers
  mod = mods.append(Modifier.Types.SUBSURF)   # add a new subsurf modifier
  mod[Modifier.Settings.LEVELS] = subSurfLevel # set subsurf subdivision levels to 3
  image.addText(text=str(subSurfLevel),width=1,height=0.4,
  location=[-0.5,subSurfLevel-0.2,0],align='RIGHT',
  color=[0.0,1.0,0.0])
  
image.save('SubSurfaceExample.jpg')



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