Here's the Python code for my PyODE Example 3. In order to run the script below, you will first need to install PyODE, and cgkit for the Renderman bindings.

#!/usr/bin/python
#
# python_render_ex3

import sys, os, random
from math import *
from cgkit.cgtypes import *
from cgkit.riutil import *
import ode


def draw_body(body):
    """
    Draw the body with Renderman primitives.
    """
    (x, y, z) = body.getPosition()
    R = body.getRotation()
    # Construct the transformation matrix
    T = mat4()
    T[0,0] = R[0]
    T[0,1] = R[1]
    T[0,2] = R[2]
    T[1,0] = R[3]
    T[1,1] = R[4]
    T[1,2] = R[5]
    T[2,0] = R[6]
    T[2,1] = R[7]
    T[2,2] = R[8]
    T[3] = (x ,y ,z , 1.0)
    RiTransformBegin()
    # Apply rotation & translation
    RiTransform(T.toList())
    (sx, sy, sz) = body.boxsize
    # Draw the body
    RiScale(sx, sy, sz)
    RiAttributeBegin()
    RiColor([ 1.0, 0.6, 0.0 ])
    RiPolygon(P=[ -0.5,-0.5,-0.5, -0.5,-0.5, 0.5, -0.5, 0.5, 0.5, -0.5, 0.5,-0.5 ]) # left side
    RiPolygon(P=[  0.5, 0.5,-0.5,  0.5, 0.5, 0.5,  0.5,-0.5, 0.5,  0.5,-0.5,-0.5 ]) # right side
    RiPolygon(P=[ -0.5, 0.5,-0.5,  0.5, 0.5,-0.5,  0.5,-0.5,-0.5, -0.5,-0.5,-0.5 ]) # front side
    RiPolygon(P=[ -0.5,-0.5, 0.5,  0.5,-0.5, 0.5,  0.5, 0.5, 0.5, -0.5, 0.5, 0.5 ]) # back side
    RiPolygon(P=[ -0.5, 0.5, 0.5,  0.5, 0.5, 0.5,  0.5, 0.5,-0.5, -0.5, 0.5,-0.5 ]) # top
    RiPolygon(P=[ -0.5,-0.5,-0.5,  0.5,-0.5,-0.5,  0.5,-0.5, 0.5, -0.5,-0.5, 0.5 ]) # bottom
    RiAttributeEnd()
    RiTransformEnd()

def create_box(density, lx, ly, lz):
    """
    Create a box body and its corresponding geom.
    """
    # Create body
    body = ode.Body(world)
    M = ode.Mass()
    M.setBox(density, lx, ly, lz)
    body.setMass(M)
    # Set parameters for drawing the body
    body.shape = "box"
    body.boxsize = (lx, ly, lz)
    # Create a box geom for collision detection
    geom = ode.GeomBox(space, lengths=body.boxsize)
    geom.setBody(body)
    return(body, geom)

def drop_object():
    """
    Drop an object into the space.
    """
    global bodies, geom
    # Create the box
    (body, geom) = create_box(1000, 1.0, 0.2, 0.2)
    # Set a random position
    body.setPosition( (random.gauss(0, 0.1), 3.0, random.gauss(0, 0.1)) )
    # Set a random rotation
    m = mat3().rotation(objcount*pi/2 + random.gauss(-pi/4, pi/4), (0, 1, 0))
    body.setRotation(m.toList())
    # Append the body and geom to a list
    bodies.append(body)
    geoms.append(geom)

# Collision callback
def near_callback(args, geom1, geom2):
    """
    Callback function for the collide method. This function checks if the given
    geoms do collide and creates contact joints if they do.
    """
    # Check if the objects do collide
    contacts = ode.collide(geom1, geom2)
    # Create contact joints
    (world, contactgroup) = args
    for c in contacts:
        c.setBounce(0.2)
        c.setMu(5000)
        j = ode.ContactJoint(world, contactgroup, c)
        j.attach(geom1.getBody(), geom2.getBody())


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

# Set the output rib file
renderer = "pyode_ex3.rib"

# Create the header
RiBegin(renderer)
RiuDefaultHeader()

# Set the width & height for each frame
width = 640
height = 480

# Set the number of frames, iterations/frames ratio and number of iterations
NFRAMES = 300
R = 4
N = (NFRAMES - 1)*R + 1

#N = 1201
#NFRAMES = 4
#R = (N - 1)/(NFRAMES - 1)

# Set the frame rate and iteration time step
fps = 25
dt = 1.0/fps/R

# Initialize the random number generator
random.seed(0)

# Create a world object
world = ode.World()
world.setGravity( (0, -9.81, 0) )
world.setERP(0.8)
world.setCFM(1E-5)

# Create a space object
space = ode.Space()

# Create a plane geom which prevent the objects from falling forever
floor = ode.GeomPlane(space, (0, 1, 0), 0)

# A list with ODE bodies
bodies = []

# The geoms for each of the bodies
geoms = []

# A joint group for the contact joints that are generated whenever two bodies collide
contactgroup = ode.JointGroup()

# Initialize the simulation
frame = 0
state = 0
counter = 0
objcount = 0

# Run the simulation
for n in range(N):
    # Detect collisions and create contact joints
    space.collide((world, contactgroup), near_callback)

    # Simulation step
    world.step(dt)

    # Remove all contact joints
    contactgroup.empty()

    counter += 1
    if (state == 0):
        if (counter == 10*R):
            # Drop an object
            drop_object()
            objcount += 1
            counter = 0
        if objcount == 20:
            state = 1
            counter = 0

    if (n % R == 0):
        # Create a frame
        frame += 1
        filename = "pyode_ex3.%03d.tif" % frame

        RiFrameBegin(frame)
        # Set the projection
        RiProjection(RI_PERSPECTIVE, fov=22)

        # Create a view transformation and apply
        V = mat4(1).lookAt(2.0*vec3(-2, 3, -4), (0, 0.5, 0), up=(0, 1, 0))
        V = V.inverse()
        RiTransform(V.toList())

        # Set the output file and frame format
        RiDisplay(filename, RI_FILE, RI_RGBA)
        RiFormat(width, height, 1.0)

        # Apply sampling
        RiPixelSamples(4,4)
        RiShadingInterpolation(RI_SMOOTH)

        # Begin the world    
        RiWorldBegin()
        # Make objects visible to rays
        RiDeclare("diffuse", "integer")
        RiAttribute("visibility", "diffuse", 1)
        RiDeclare("bias", "float")
        RiAttribute("trace", "bias", 0.005)
        # Don't interpolate irradiance
        RiDeclare("maxerror", "float")
        RiAttribute("irradiance", "maxerror", 0.0)
        # Apply global illumination
        RiDeclare("samples", "integer")
        RiSurface("occsurf2", "samples", 256)

        # Create a white ground plane
        RiAttributeBegin()
        RiColor([ 0.9, 0.9, 0.9 ])
        RiPolygon(P=[ -20, 0, 20, 20, 0, 20, 20, 0, -20, -20, 0, -20 ])
        RiAttributeEnd()

        # Draw the bodies
        for b in bodies:
            draw_body(b)

        # Complete the world & frame
        RiWorldEnd()
        RiFrameEnd()

# Create the termination line
RiEnd()

Here's the command pipeline to generate an mp4 video:

First, create the rib file with the pyode_render_ex3.py script:

python pyode_render_ex3.py

Compile the shader:

sdrc occsurf2.sl

Then render with Pixie:

rndr -p pyode_ex3.rib

Crop the the tif files and convert to png using convert_frames.pl:

perl convert_frames.pl

Assemble the frames into an uncompressed avi with VirtualDubMod (Windows) and these processing settings:

VirtualDubMod /spyode_ex3.vcf /x

Compress the avi with x264 and this AVISynth file:

x264 --crf 16 -o pyode_ex3.mp4 pyode_ex3.avs