Skip to content

Python driven Blender

Ben Heasly edited this page Nov 9, 2013 · 66 revisions

This is a guide on procedural scene generation using the Blender-Python API that ships with Blender.

Step 0: Setting up.

To craft a scene using Python scripts, it is imperative to start Blender from within a terminal. This is the only way that you can see error messages generated by Blender's engine and any output generated by your print statements. In Mac OSX, generate a file called startBlender.command and enter the following into it.

#!/bin/sh
/Applications/Blender/blender.app/Contents/MacOS/blender

Next, go to Finder and double click on the startBlender.command icon. Blender should launch together with a Terminal.app.

In Blender, click on the Screen Layout button (button to the right of Help) in the Information viewport, and select the Scripting mode. This will open the following viewports: Text Editor, Python Console and 3D View.

In the Text Editor viewport, click on Text -> Create Text Block (or alternatively, press Control+N). This will create an empty document. Enter the following print command,

print('Hello from the Blender-Python API !');

then hit the Run Script button. You should see Hello from the Blender-Python API ! in the Terminal that launched with Blender. Great!

We will now proceed with the procedural generation of a scene using the Blender-Python API (bpy.py) module. To ease this process, we have created a Python module (called SceneUtilsV1.py) which contain methods for generating, configuring, and positioning various scene components. Using this module the user can experience the power of procedurally-generated scenes without having to first learn the intricacies of the low-level bpy.py module. More experienced users are welcome to modify and extend the supplied module to fit their needs.

Download the SceneUtilsV1.py module. In the following, it is assumed that the SceneUtilsV1.py module exists at /Users/Shared/PythonDrivenBlender/Toolboxes. It is also assumed that an empty directory named ColladaExports exists at /Users/Shared/PythonDrivenBlender. Let's begin.

Step 1: Importing the necessary modules.

In the Text Editor viewport, create a new Text Block and type the following:

# Import the Blender-Python API module
import bpy
# Import necessary system modules
import sys
import imp
# Import necessary math functions from the math and mathutils modules
from math import pi, floor
from mathutils import Vector

# Set the root, toolboxes, and exports directories
rootDirectory    = '/Users/Shared/PythonDrivenBlender';  
toolboxDirectory = '{}/Toolboxes'.format(rootDirectory);
exportsDirectory = '{}/ColladaExports'.format(rootDirectory);

# Append the toolboxDirectory to the Blender scripts path
sys.path.append(toolboxDirectory);
 
# Import our custom scene toolbox module
import SceneUtilsV1;
imp.reload(SceneUtilsV1);

# Notify user that all is good so far.
print('All ok with the imports')

Hit Text->Save as and save this file as SceneTutorial.py. Then hit the Run Script button. Check the terminal. If all the paths have been set up correctly you should see All ok with the imports. If you see an Error: message, check your paths.

Step 2: Initializing the scene.

In the Text Editor viewport, add the following:

# Generate a dictionary with various scene initialization parameters
params = { 'name'               : 'CheckerShadowScene',  # name of new scene
           'erasePreviousScene' : True,                  # erase existing scene
           'sceneWidthInPixels' : 1024,                  # 1024 pixels along the horizontal-dimension
           'sceneHeightInPixels': 768,                   # 768 pixels along the vertical-dimension
           'sceneUnitScale'     : 1.0/100.0,             # set unit scale to 1.0 cm
           'sceneGridSpacing'   : 10.0/100.0,            # set the spacing between grid lines to 10 cm
           'sceneGridLinesNum'  : 20,                    # display 20 grid lines
         };
# Instantiate a sceneManager object with the above params
scene = SceneUtilsV1.sceneManager(params);

Save the code. Before re-running the script, note that the 3D view contains Blender's stock scene. Now hit the Run Script button. Note that the 3D viewport is now empty, only displaying a 20x20 spatial grid.

The above code block, instantiates a sceneManager object. The sceneManager class is defined in the SceneUtilsV1 module, and contains several utility methods for managing a Blender scene. This particular initialization, asks to erase all components of the previous scene, sets the size of the rendered image to 1024x768 (this can be changed in RenderToolbox3), specifies a metric coordinate system in which units are 1 cm, and sets a 20x20 display grid with a 10 cm line separation.

Check the terminal. You should see no errors, and a bunch of messages, like so:

Erasing previous scene components
Unlinking object "Camera", from old scene ("Scene")
Unlinking object "Cube", from old scene ("Scene")
Unlinking object "Lamp", from old scene ("Scene")
Clearing all users for mesh "Cube"
Removing mesh "Cube", from old scene ("Scene")
Clearing all users for lamp "Lamp"
Removing lamp "Lamp", from old scene ("Scene")
Clearing all users for camera "Camera"
Removing camera "Camera", from old scene ("Scene")
Clearing all users for material "Material"
Removing material "Material", from old scene ("Scene")
Removing object "Camera", from old scene ("Scene")
Removing object "Cube", from old scene ("Scene")
Removing object "Lamp", from old scene ("Scene")

These are generated by the constructor method of the sceneManager object as it erases the contents of the previous scene.

Step 3: Generating materials for different scene components.

In the Text Editor viewport, add the following:

# Generate the materials
# 1. Generate dictionary with specs for the cylinder material. These can be changed in RenderToolbox3.
params = { 'name'              : 'cylinderMaterial',           # tag with which RenderToolbox3 can access this material
           'diffuse_shader'    : 'LAMBERT',
           'diffuse_intensity' : 0.5,
           'diffuse_color'     : Vector((0.0, 1.0, 0.0)),
           'specular_shader'   : 'WARDISO',
           'specular_intensity': 0.1,
           'specular_color'    : Vector((0.0, 1.0, 0.0)),
           'alpha'             : 1.0
         }; 
cylinderMaterialType = scene.generateMaterialType(params);

# 2. Generate dictionary with specs for the room (walls) material. These can be changed in RenderToolbox3.
params = { 'name'              : 'roomMaterial',              # tag with which RenderToolbox3 can access this material
           'diffuse_shader'    : 'LAMBERT',
           'diffuse_intensity' : 0.5,
           'diffuse_color'     : Vector((0.6, 0.6, 0.6)),
           'specular_shader'   : 'WARDISO',
           'specular_intensity': 0.0,
           'specular_color'    : Vector((1.0, 1.0, 1.0)),
           'alpha'             : 1.0
         }; 
roomMaterialType = scene.generateMaterialType(params);

# 3. Generate dictionary with specs for the cloth material. These can be changed in RenderToolbox3.
params = { 'name'              : 'clothMaterial',            # tag with which RenderToolbox3 can access this material
           'diffuse_shader'    : 'LAMBERT',
           'diffuse_intensity' : 0.5,
           'diffuse_color'     : Vector((0.7, 0.0, 0.1)),
           'specular_shader'   : 'WARDISO',
           'specular_intensity': 0.0,
           'specular_color'    : Vector((0.7, 0.0, 0.1)),
           'alpha'             : 1.0
         }; 
clothMaterialType = scene.generateMaterialType(params);

# 4. Generate dictionary with specs for the dark check material. These can be changed in RenderToolbox3.
params = { 'name'              : 'darkCheckMaterial',        # tag with which RenderToolbox3 can access this material
           'diffuse_shader'    : 'LAMBERT',
           'diffuse_intensity' : 0.1,
           'diffuse_color'     : Vector((0.5, 0.5, 0.5)),
           'specular_shader'   : 'WARDISO',
           'specular_intensity': 0.0,
           'specular_color'    : Vector((0.5, 0.5, 0.5)),
           'alpha'             : 1.0
         }; 
darkCheckMaterialType = scene.generateMaterialType(params);

# 5. Generate dictionary with specs for the light check material. These can be changed in RenderToolbox3.
params = { 'name'              : 'lightCheckMaterial',       # tag with which RenderToolbox3 can access this material
           'diffuse_shader'    : 'LAMBERT',
           'diffuse_intensity' : 0.7,
           'diffuse_color'     : Vector((0.55, 0.55, 0.40)),
           'specular_shader'   : 'WARDISO',
           'specular_intensity': 0.0,
           'specular_color'    : Vector((0.5, 0.5, 0.5)),
           'alpha'             : 1.0
         }; 
lightCheckMaterialType = scene.generateMaterialType(params);

# Generate list of materials
checkBoardMaterialsList = [lightCheckMaterialType, darkCheckMaterialType];

Save the text and hit the Run Script button. The above code block, generates a bunch of different materials for the various scene components. Check the Terminal. There should be no error messages and no other additional messages. Also there should be no changes in the 3D viewport.

Step 4: Adding a lamp.

In the Text Editor viewport, add the following:

# Generate an area lamp model
params = {'name'           : 'areaLampModel',    # tag with which RenderToolbox3 can access this lamp model
          'color'          : Vector((1,1,1)),    # white color
          'fallOffDistance': 120,                # distance at which intensity falls to 0.5 
          'width1'         : 20,                 # width of the area lamp
          'width2'         : 15                  # height of the area lamp
         }
brightLight100 = scene.generateAreaLampType(params);
 
# Position of lamp
leftAreaLampPosition = Vector((
                            -56,      # horizontal position (x-coord)
                             56,      # depth position (y-coord)
                             16       # elevation (z-coord)
                           ));
# Point at which the lamp is directed to
leftAreaLampLooksAt = Vector((
                            -71,          # horizontal position (x-coord)
                             71,          # depth position (y-coord)
                             20           # elevation (z-coord)
                           ));
# Generate dictionary containing the lamp name and model, its location and direction
params = { 'name'     : 'leftAreaLamp',   # tag with which RenderToolbox3 can access this lamp object
           'model'    : brightLight100, 
           'showName' : True, 
           'location' : leftAreaLampPosition, 
           'lookAt'   : leftAreaLampLooksAt
         };
# Add the lamp to the scene
leftAreaLamp = scene.addLampObject(params);

The above code block, generates an area lamp model with a desired size, intensity drop-off, and color and adds an instance of this lamp object. Save the text and hit the Run Script button. Note that the lamp object is placed near the rear-left region of the scene and that it is pointing away from the center of the scene, as seen by the dotted yellow line.

Step 5:Adding a second lamp.

In the Text Editor viewport, add the following:

# Position of lamp
frontAreaLampPosition = Vector((
                              0,      # horizontal position (x-coord)
                            -50,      # depth position (y-coord)
                             50       # elevation (z-coord)
                             ));
# Point at which the lamp is directed to
frontAreaLampLooksAt = Vector((
                              0,      # horizontal position (x-coord)
                            -90,      # depth position (y-coord)
                             90       # elevation (z-coord)
                             ));
# Generate dictionary containing properties of the second lamp.
params = { 'name'     : 'frontAreaLamp',      # tag with which RenderToolbox3 can access this lamp object
           'model'    : brightLight100, 
           'showName' : True, 
           'location' : frontAreaLampPosition, 
           'lookAt'   : frontAreaLampLooksAt
          };
# Add the lamp to the scene
frontAreaLamp = scene.addLampObject(params);

The above code block adds a second instance of the same lamp object. Save the text and hit the Run Script button. Note that the second lamp object is placed in the front region of the scene and that it is also pointing away from the center of the scene, as seen by the dotted yellow line.

Step 6: Adding a camera.

In the Text Editor viewport, add the following:

nearClipDistance = 0.1;   
farClipDistance  = 300;
# Generate dictionary containing our camera specs.
params = {'clipRange'            : Vector((nearClipDistance ,  farClipDistance)),  # clipping range (depth)
          'fieldOfViewInDegrees' : 36,                                             # horizontal FOV
          'drawSize'             : 2,                                              # camera wireframe size
         };
# Generate camera model
cameraType = scene.generateCameraType(params);
 
# Generate dictionary containing our camera's name and model, its location and direction.
cameraHorizPosition = -57;
cameraDepthPosition = -74;
cameraElevation     = 45;
params = { 'name'       : 'Camera',                  # tag with which RenderToolbox3 can access this camera object
           'cameraType' : cameraType,
           'location'   : Vector((cameraHorizPosition, cameraDepthPosition, cameraElevation)),     
           'lookAt'     : Vector((-13,-17,10)),
           'showName'   : True,
          };   
# Add an instance of this camera model       
mainCamera = scene.addCameraObject(params);

The above code block generates a camera model with a desired clipping range and field of view, and adds an instance of this camera object to the scene. Save the text and hit the Run Script button. The camera object is inserted to the scene. The black line indicates the clipping range of the camera.

Step 7: Generating the checkerboard.

In the Text Editor viewport, add the following:

# Define the checkerboard geometry
boardThickness    = 2.5;
boardHalfWidth    = 14;
tilesAlongEachDim = 4;        
boardIsDimpled    = True;

# Compute checker size
N                 = floor(tilesAlongEachDim/2);
deltaX            = boardHalfWidth/N;
deltaY            = deltaX;

# Generate dictionary with tile parameters
tileParams = { 'name'     : '',
               'scaling'  : Vector((deltaX/2, deltaY/2, boardThickness/2)),
               'rotation' : Vector((0,0,0)), 
               'location' : Vector((0,0,0)),
               'material' : checkBoardMaterialsList[0]
             };
                  
# Add the checks of the checkerboard
for ix in list(range(-N,N+1)):
    for iy in list(range(-N,N+1)): 
        tileParams['name']     = 'floorTileAt({0:1d},{1:2d})'.format(ix,iy);
        tileParams['location'] =  Vector((ix*deltaX, iy*deltaY, boardThickness*0.5));
        tileParams['material'] =  checkBoardMaterialsList[(ix+iy)%2];
        theTile = scene.addCube(tileParams);

The above code block generates a number of cube objects, tiles them, and assigns an alternating material to each tile. Save the text and hit the Run Script button. The checkerboard is inserted to the scene.

Step 7a: Dimpling the checkerboard (optional).

In the Text Editor viewport, add the following:

# Modify the checks if the checkerboard is dimpled
if boardIsDimpled:
   # Generate dictionary with sphere parameters
   sphereParams = { 'name'     : 'theSphere',
                    'scaling'  : Vector((1.0, 1.0, 1.0))*deltaX/2, 
                    'location' : Vector((0,0,0)),
                    'material' : checkBoardMaterialsList[0],
                  };
   indentation = 0.09;

   for ix in list(range(-N,N+1)):
       for iy in list(range(-N,N+1)):
           # Retrieve the tileObject that is to be dimpled
           tileObjectName     = 'floorTileAt({0:1d},{1:2d})'.format(ix,iy);
           theTile            = bpy.data.objects[tileObjectName];
           # Generate the sphere object that is to be used to bore out material from the tile
           theSphere          = scene.addSphere(sphereParams);
           theSphere.location = theTile.location + Vector((0,0,deltaX/2*(1-indentation)));
           # Do the carving
           scene.boreOut(theTile, theSphere, True);

The above code block dimples the checkers of the checkerboard if it so specified. This is done by generating a sphere object, positioning it over each checker, and carving out the volume that is common between the checker and the sphere. Save the text and hit the Run Script button. The checkers get dimpled.

Step 8: Adding a hollow cylinder.

In the Text Editor viewport, add the following:

cylinderWidth  = 6.2;
cylinderHeight = 11;
# Generate dictionary with properties of the cylinder's outer shell
params = { 'name'    : 'The cylinder',
               'scaling' : Vector((cylinderWidth, cylinderWidth, cylinderHeight)),
               'rotation': Vector((0,0,0)), 
               'location': Vector((-9.4, 9.4, cylinderHeight/2+boardThickness)),
               'material': cylinderMaterialType,
             }; 
# Add the cylinder (outer shell)
theCylinder = scene.addCylinder(params);

deltaHeight     = 1.4;
cylinderWidth  *= 0.85;
cylinderHeight -= deltaHeight;
# Generate dictionary with properties of the cylinder core 
params = { 'name'     : 'The cylinder core',
           'scaling'  : Vector((cylinderWidth, cylinderWidth, cylinderHeight)),
           'rotation' : Vector((0,0,0)), 
           'location' : Vector((-9.4, 9.4, cylinderHeight/2 + deltaHeight+boardThickness)),
           'material' : cylinderMaterialType,
         }; 
# Generate the cylinder core
theCylinderCore = scene.addCylinder(params);

# Carve the cylinder core from the external shell
scene.boreOut(theCylinder, theCylinderCore, True);

The above code generates a hollow cylinder by carving a cylindrical core out of an outer cylinder. Save the text and hit the Run Script button. A hollow cylinder is inserted to the scene.

Step 9: Adding a cloth .

In the Text Editor viewport, add the following:

# Generate an elevation map representing a creased cloth-like material.
xBinsNum = 501;  
yBinsNum = 501;
elevationMap = SceneUtilsV1.createRandomGaussianBlobsMap(xBinsNum, yBinsNum);

# Generate dictionary with properties of the cloth object
params = { 'name'         : 'The Cloth',
           'scale'        : Vector((boardHalfWidth*4.0, boardHalfWidth*4.0, boardThickness*.4)),
           'rotation'     : Vector((0,0,0)),
           'location'     : Vector((0,0, 0.05)), 
           'xBinsNum'     : xBinsNum,
           'yBinsNum'     : yBinsNum,
           'elevationMap' : elevationMap,
           'material'     : clothMaterialType,
         };
# Add the cloth object
theCloth = scene.addElevationMapObject(params);

The above code generates a cloth-like mesh generated by blitting 300 elongated Gaussians whose orientation is systematically shifted. Save the text and hit the Run Script button. Depending on the speed of your computer, this process may take up to several minutes. Note the wavy red cloth-like material that is is inserted between the checkerboard and the room floor.

Step 10: Adding the enclosing room .

In the Text Editor viewport, add the following:

# Generate dictionary with properties of the enclosing room
params = { 'floorName'             : 'floor',
           'backWallName'          : 'backWall',
           'frontWallName'         : 'frontWall',
           'leftWallName'          : 'leftWall',
           'rightWallName'         : 'rightWall',
           'ceilingName'           : 'ceiling',
           'floorMaterialType'     : roomMaterialType,
           'backWallMaterialType'  : roomMaterialType,
           'frontWallMaterialType' : roomMaterialType,
           'leftWallMaterialType'  : roomMaterialType,
           'rightWallMaterialType' : roomMaterialType,
           'ceilingMaterialType'   : roomMaterialType,
           'roomWidth'     : 180,
           'roomDepth'     : 180,
           'roomHeight'    : 100,
           'roomLocation'  : Vector((0,0,0))
          }
# Add the enclosing room
scene.addRoom(params);

Step 11: Exporting to collada file .

In the Text Editor viewport, add the following:

scene.exportToColladaFile('exportsDirectory');

Save the text and hit the Run Script button. A collada file with a .dae extension will be generated in the exportsDirectory.


Generated 3D model

When finished, the generated scene model displayed in Blender's 3D viewport should look like the one shown in panel (a) below. If you zoom in, the internal scene should look like the one shown in the panel (b) below.

Rendered scene (non-dimpled checkerboard).

The RenderToolbox-3 rendered scene (Mitsuba) is shown below.

Rendered scene (dimpled checkerboard).

The RenderToolbox-3 rendered scene (Mitsuba) is shown below.

See BlenderPython for more about rendering the scene with RenderToolbox3.

Clone this wiki locally