Vous êtes sur la page 1sur 4

Introduction to Procedural Run Programs

Procedural Run Programs are a simple and efficient way to inject programmability into your RIB, without having to delve
into the dark art of DSO writing. Run Programs are simply a call to an external program or script, (Python, PERL, C++,
etc), that returns valid RIB on the stdout stream back to RenderMan. RenderMan picks up these RIB calls and renders
them as if they had come from Maya or an RIB Archive. In this example, we walk through the mechanics of a simple
Procedural "Run Program" using Python.

Run Programs use standard streams to pass information, both data in and RIB out. The programs should look for input
on the stdin (Standard Input), then return information to stdout (Standard Output). The example below is about as simple
as you can get: it uses Python's stdout stream to write out an RiSphere.
If you want to write information back out from this process, for you to debug your code, you can write to stderr
(sys.stderr.write). This will appear in Alfred; the job arrow will go blue, click this, then click the box with the blue border.
You should see your information returned.
In your Maya scene create a NURBs sphere and attach a Pre Shape MEL to it. In the Pre Shape MEL text field enter the
following code:
RiProcedural "RunProgram" "createSphere.py" -1e38 1e38 -1e38 1e38 -1e38 1e38 "";
RiAttribute "visibility" "int camera" 0 "int diffuse" 0 "int specular" 0;
Here we have two lines; the first is the call to the RunProgram using the RiProcedural call, the second sets the visibility
of our proxy shape OFF using a standard RiAttribute call. The RiProcedural call starts with the qualifier "RunProgram"
which tells RenderMan what type of procedural to expect - RunProgram, DynamicLoad or a DelayedReadArchive.
The next argument is our python script. In Linux, this is just the path to the script. In Windows you need to pass "python
createSphere.py" and make sure the python executable is in your path. After this is the bounding box of the RIB that is
going to be generated listed as six floating point numbers: xmin, xmax, ymin, ymax, zmin, zmax in the current object
space. If you do not know your bounding box because it is dynamically generated, set it LARGE to ensure it does not get
culled before the script is run.
Finally we have a string argument (here it is empty) which is used as a data block that gets passed to the program.
#! /usr/bin/env python
# createSphere.py
# Description - makes an RiSphere
import sys
sys.stdout.write('Sphere [3 -3 3 360]\n')
sys.stdout.write('\377')
sys.stdout.flush()
This is a trivial example which makes a sphere. We import the sys module which gives us access to input and output
from the user. Then we just write out an RiSphere call to stdout ensuring that we use proper RIB syntax and formatting.
This is followed by two critical lines: writing \377 and flushing the stdout. "\377" is an octal number for FINISH. Flushing
makes sure that everything gets written to the output stream.
Click render and, if you have your python environment set up correctly, a sphere should render. Ok not the best example,
but you have just written your first Run Program (or rather, copied it out).
If you are developing your python scripts in a Unix environment, you will want to start your files with the following line:

#! /usr/bin/env python

Then set the permissions on the file to be executable using chmod:

chmod +x myscript.py

Also note that Maya 8.5 sets an environment variable called PYTHONHOME that can sabotage your ability to render
from RenderMan Studio. To remove this, go to the Script Editor in Maya, then in a Python tab enter this:
import os
del( os.environ['PYTHONHOME'] )
Adding arguments to your RunProgram
Now we want to generate simple cube pass arguments to the Run Program. To keep it simple, we will rotate the cube
about its Y axis using the RiRotate call. The angle will be taken from an argument within the Pre Shape MEL text field,
but the drive the point home, we shall make it scale the cube as well.
RiProcedural "RunProgram" "scaleSphere.py" -1e38 1e38 -1e38 1e38 -1e38 1e38 "2 .5 1";

#! /usr/bin/env python
#scaleSphere.py
#RiProcedural "RunProgram" "scaleSphere.py" -1e38 1e38 -1e38 1e38 -1e38 1e38 "2 .5 1";
import sys, string
args = sys.stdin.readline()
while(args):
words = string.split(args)
sys.stdout.write('AttributeBegin\n')
sys.stdout.write('Scale %s %s %s\n' % (words[1], words[2], words[3]))
sys.stdout.write('Sphere [3 -3 3 360]\n')
sys.stdout.write('AttributeEnd\n')
sys.stdout.write('\377')
sys.stdout.flush()
# read the next line
args = sys.stdin.readline()
The code has only changed slightly. As we are interested in getting information from the user, we need to read from stdin.
This is where to add the line that puts these into a variable called args. For each argument given by the user, we place it
into a list called words. We then use these values in the scale command by substituting with %s (which means a string
value). Here the RiSphere is scaled (2, 0.5, 1) respectively.
The while loop above is a common construction to almost all Run Programs that take arguments in the data block. What
RenderMan does, when executing the script, is pass the data block arguments in on stdin. RenderMan continues
sending data along the stdin as long as there are calls to the same script within the RIB file. This means the script stays
in memory and can maintain state from one invocation to the next. The loop is set up to read stdin, process the line of
arguments, then look for another. If there is nothing more on stdin, the script should exit.
Getting Information from your scene.
Lets look at getting information from the Maya scene to use in our run program. We do not have to type in values to the
Pre Shape MEL text field to get them into our run program. We can, if desired, write information onto a shape node, read
this off, then pass this to the run program. Here our code changes slightly. I want to find out where my object is in the
scene; for this we need to query the transform node, not the shape node (RenderMan's default place to look). I then pass
this location to the run program command and hey presto!
float $pos[] = `xform -q -ws -t ${OBJNAME}`;
RiProcedural "RunProgram" "findPos.py" "$pos[0] $pos[1] $pos[2]" "-1e8 1e8 -1e8 1e8 -1e8 1e8";
#! /usr/bin/env python
#findPos.py
#Description - generate a sphere at the position of
#another object in the scene
import sys, string, random
args = sys.stdin.readline()
while(args):
words = string.split(args)
pos = words[1:4]
sys.stdout.write('AttributeBegin\n')
sys.stdout.write(' TransformBegin\n')
sys.stdout.write('
Translate %s %s %s\n' % (pos[0], pos[1], pos[2]))
sys.stdout.write(' TransformEnd\n')
sys.stdout.write(' Sphere 1 -1 1 360\n')
sys.stdout.write('AttributeEnd\n')
sys.stdout.write('\377')
sys.stdout.flush()
# read the next line
args = sys.stdin.readline()

Making decisions in your code


As our program can churn out any RIB we want (as long as it has legal syntax), you can output certain RIB under certain

conditions. Below we read an attribute off the shape node and compare it to current frame number of the animation. If the
onTime (the value on the shape node) is greater than the current frame it will output the code to the RIB, otherwise it will
not output anything.
float $time = `currentTime -q`;
float $pos[] = `xform -q -ws -t ${OBJNAME}`;
float $onTime = `getAttr ${OBJNAME}.onTime`;
RiProcedural "RunProgram" "onTime.py" "$pos[0] $pos[1] $pos[2] $time $onTime" "-1e8 1e8 -1e8 1e8 -1e8 1e8";
#! /usr/bin/env python
#onTime.py
#Description - generate a sphere at the position of
#another object in the scene
#RiProcedural "RunProgram" "onTime.py" "$pos[0] $pos[1] $pos[2] $time
import sys, string, random
args = sys.stdin.readline()
while(args):
words = string.split(args)
pos = words[1:4]
currentTime = string.atoi(words[4])
onTime = string.atoi(words[5])
sys.stderr.write(repr(len(words))+ '\n')
sys.stderr.write('Position: %s %s %s\n' % (pos[0], pos[1], pos[2]))
sys.stderr.write('Current Time: %s\n' % (currentTime))
sys.stderr.write('On Time: %s\n' % (onTime))
if(onTime < currentTime):
sys.stdout.write('AttributeBegin\n')
sys.stdout.write(' TransformBegin\n')
sys.stdout.write('
Translate %s %s %s\n' % (pos[0], pos[1], pos[2]))
sys.stdout.write(' TransformEnd\n')
sys.stdout.write(' Sphere 1 -1 1 360\n')
sys.stdout.write('AttributeEnd\n')
else:
sys.stdout.write('')
sys.stdout.write('\377')
sys.stdout.flush()
args = sys.stdin.readline()

Creating Random Points in a Sphere


Here we can abuse the RiPoints primitive to generate 1000s of points in a spherical shape. The arguments are the
number of points you want, the width of the points, and the radius of the sphere.
RiProcedural "RunProgram" "sphereRand.py" "2000 0.1 2" "-1e38 1e38 -1e38 1e38 -1e38 1e38";
#! /usr/bin/env python
#sphereRand.py
#Description - make a sphere of random sized spheres
import sys, string, random, math
def randomPoint(rangeMin, rangeMax):
val = (random.uniform(rangeMin, rangeMax), random.uniform(rangeMin, rangeMax), random.uniform(rangeMin,
rangeMax))
return val
def formatTuple(theTuple):
return (repr(theTuple[0]) + " " + repr(theTuple[1]) + " " + repr(theTuple[2]))
def scalePoint(point, s):
return ((point[0] * s, point[1] * s , point[2] * s))

def normalise(point):
mag = math.sqrt(point[0]*point[0] + point[1]*point[1] + point[2]*point[2])
return (point[0]/mag,point[1]/mag,point[2]/mag)
def randWidth(max):
return random.uniform(0,max)
args = sys.stdin.readline()
while(args):
words = string.split(args)
number = string.atoi(words[1])
width = string.atof(words[2])
radius = string.atof(words[3])
sys.stdout.write('AttributeBegin\n')
sys.stdout.write('Points \"P\" [')
for i in range(0,number):
point = randomPoint(-1,1)
normPoint = normalise(point)
scalePnt = scalePoint(normPoint, radius)
sys.stdout.write('%s ' % formatTuple(scalePnt))
sys.stdout.write(']\n')
sys.stdout.write('\"width\" [')
for i in range(0,number):
sys.stdout.write('%s ' % randWidth(width))
sys.stdout.write(']\n')
sys.stdout.write('\"Cs\" [')
for i in range(0,number):
sys.stdout.write('%s ' % formatTuple(randomPoint(0,1)))
sys.stdout.write(']\n')
sys.stdout.write('AttributeEnd\n')
sys.stdout.write('\377')
sys.stdout.flush()
args = sys.stdin.readline()

Vous aimerez peut-être aussi