procedural run programs

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";

Upload: rendermanuser

Post on 12-Nov-2015

215 views

Category:

Documents


0 download

DESCRIPTION

Procedural Run Programs

TRANSCRIPT

  • 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 syssys.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 osdel( 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, stringargs = 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()

    Introduction to Procedural Run Programs Adding arguments to your RunProgram Getting Information from your scene. Making decisions in your code Creating Random Points in a Sphere