# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# -------------------------------------------------------------
# The RenderMan (R) Interface Procedures and Protocol are:
# Copyright 1988, 1989, 2000, Pixar
# All Rights Reserved
#
# RenderMan (R) is a registered trademark of Pixar
# -------------------------------------------------------------
# $Id: ri.py,v 1.4 2006/02/14 19:29:39 mbaas Exp $

"""A RenderMan(R) binding for Python.

This module contains a complete binding of Pixar's RenderMan API. The
binding was written to be compliant to v3.2 of Pixar's RenderMan
Interface specification. However, it also supports some newer features
such as string handles for light sources or object instances.

It is safe to import the module using 

  from cgkit.ri import *

All the functions that get imported start with the prefix Ri, all
constants start with RI_ or RIE_, so you probably won't get into a
naming conflict.

After importing the module this way you can use the functions just as
you are used to from the C API (well, almost).

  from cgkit.ri import *

  RiBegin(RI_NULL)
  RiWorldBegin()
  RiSurface("plastic")
  RiSphere(1,-1,1,360)
  RiWorldEnd()
  RiEnd()

For more details on using the module see the cgkit manual at
http://cgkit.sourceforge.net/
"""

import sys, types, time, os, os.path, string, getpass, inspect, gzip
try:
    from _core import vec3 as _vec3
except:
    from cgtypes import vec3 as _vec3

########################### Constants #############################

RI_NULL         = None

RI_TRUE         = 1
RI_FALSE        = 0

RI_HIDDEN       = "hidden"
RI_PAINT        = "paint"

RI_EPSILON      = 1.0e-10
RI_INFINITY     = 1.0e38

RI_FILE         = "file"
RI_FRAMEBUFFER  = "framebuffer"
RI_RGB          = "rgb"
RI_RGBA         = "rgba"
RI_RGBZ         = "rgbZ"
RI_RGBAZ        = "rgbaz"
RI_A            = "a"
RI_Z            = "z"
RI_AZ           = "az"

RI_ORIGIN       = "origin"

RI_PERSPECTIVE  = "perspective"
RI_ORTHOGRAPHIC = "orthographic"
RI_FOV          = "fov"

RI_LH           = "lh"
RI_RH           = "rh"
RI_INSIDE       = "inside"
RI_OUTSIDE      = "outside"

RI_BILINEAR     = "bilinear"
RI_BICUBIC      = "bicubic"

RI_LINEAR       = "linear"
RI_CUBIC        = "cubic"

RI_CONSTANT     = "constant"
RI_SMOOTH       = "smooth"

RI_P            = "P"
RI_PW           = "Pw"
RI_PZ           = "Pz"
RI_N            = "N"
RI_NP           = "Np"
RI_NG           = "Ng"
RI_CI           = "Ci"
RI_OI           = "Oi"
RI_CS           = "Cs"
RI_OS           = "Os"
RI_S            = "s"
RI_T            = "t"
RI_ST           = "st"

RI_COMMENT      = "comment"
RI_STRUCTURE    = "structure"
RI_VERBATIM     = "verbatim"

RI_HERMITESTEP    = 2
RI_CATMULLROMSTEP = 1
RI_BEZIERSTEP     = 3
RI_BSPLINESTEP    = 1
RI_POWERSTEP      = 4

RI_PERIODIC     = "periodic"
RI_NONPERIODIC  = "nonperiodic"
RI_CLAMP        = "clamp"
RI_BLACK        = "black"

RI_FLATNESS     = "flatness"

RI_PRIMITIVE    = "primitive"
RI_UNION        = "union"
RI_DIFFERENCE   = "difference"
RI_INTERSECTION = "intersection"

RI_WIDTH        = "width"
RI_CONSTANTWIDTH = "constantwidth"

RI_HOLE         = "hole"
RI_CREASE       = "crease"
RI_CORNER       = "corner"
RI_INTERPOLATEBOUNDARY = "interpolateboundary"

RI_AMBIENTLIGHT = "ambientlight"
RI_POINTLIGHT   = "pointlight"
RI_DISTANTLIGHT = "distantlight"
RI_SPOTLIGHT    = "spotlight"

RI_INTENSITY    = "intensity"
RI_LIGHTCOLOR   = "lightcolor"
RI_FROM         = "from"
RI_TO           = "to"
RI_CONEANGLE    = "coneangle"
RI_CONEDELTAANGLE = "conedeltaangle"
RI_BEAMDISTRIBUTION = "beamdistribution"

RI_MATTE        = "matte"
RI_METAL        = "metal"
RI_SHINYMETAL   = "shinymetal"
RI_PLASTIC      = "plastic"
RI_PAINTEDPLASTIC = "paintedplastic"

RI_KA           = "Ka"
RI_KD           = "Kd"
RI_KS           = "Ks"
RI_ROUGHNESS    = "roughness"
RI_KR           = "Kr"
RI_TEXTURENAME  = "texturename"
RI_SPECULARCOLOR = "specularcolor"

RI_DEPTHCUE     = "depthcue"
RI_FOG          = "fog"
RI_BUMPY        = "bumpy"

RI_MINDISTANCE  = "mindistance"
RI_MAXDISTANCE  = "maxdistance"
RI_BACKGROUND   = "background"
RI_DISTANCE     = "distance"
RI_AMPLITUDE    = "amplitude"

RI_RASTER       = "raster"
RI_SCREEN       = "screen"
RI_CAMERA       = "camera"
RI_WORLD        = "world"
RI_OBJECT       = "object"

RI_IDENTIFIER   = "identifier"
RI_NAME         = "name"
RI_SHADINGGROUP = "shadinggroup"

RI_IGNORE       = "ignore"
RI_PRINT        = "print"
RI_ABORT        = "abort"
RI_HANDLER      = "handler"

RI_HANDLEID     = "__handleid"

# Tokens specific to the cgkit binding...
RI_RIBOUTPUT    = "_riboutput"
RI_VERSION      = "_version"

# Error handling: severity levels
RIE_INFO        = 0
RIE_WARNING     = 1
RIE_ERROR       = 2
RIE_SEVERE      = 3

RIE_INCAPABLE   = 11

RIE_NOTOPTIONS  = 25       # Invalid state for options
RIE_NOTATTRIBS  = 26       # Invalid state for attributes
RIE_NOTPRIMS    = 27       # Invalid state for primitives
RIE_ILLSTATE    = 28       # Other invalid state 
RIE_RANGE       = 42       # Parameter out of range 
RIE_CONSISTENCY = 43       # Parameters inconsistent

RIE_INVALIDSEQLEN = 80     # A sequence hasn't had the required length
RIE_UNDECLARED    = 81     # An undeclared parameter is used

RiLastError     = 0

############################ Types ###################################

RtBoolean = bool
RtInt = int
RtFloat = float
RtString = str
RtToken = str
RtVoid = None
RtPointer = lambda x: x

RtColor = tuple
RtPoint = tuple
RtVector = tuple
RtNormal = tuple
RtHpoint = tuple
RtMatrix = tuple
RtBasis = tuple
RtBound = tuple

RtObjectHandle = lambda x: x
RtLightHandle = lambda x: x
RtContextHandle = lambda x: x

RtFilterFunc = lambda x: x
RtErrorHandler = lambda x: x
RtProcSubdivFunc = lambda x: x
RtProcFreeFunc = lambda x: x
RtArchiveCallback = lambda x: x


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

class RIBStream:
    """This class encapsulates the output stream.

    The version number is automatically placed into the stream before
    any "real" Ri calls are made. Output from RiArchiveRecord() will
    be placed before the version number.
    """
    
    def __init__(self, outstream):
        self.out = outstream
        self.output_version = 1

    def close(self):
        """Close the stream, unless it's stdout."""
        if self.out!=sys.stdout:
            self.out.close()

    def flush(self):
        """Flush the internal buffer."""
        self.out.flush()

    def write(self, data):
        """Write data into the stream."""
        if self.output_version:
            self.out.write('version 3.03\n')
            self.output_version = 0
        self.out.write(data)

    def writeArchiveRecord(self, data):
        """Same as write() but suppresses the version number.

        This method is used by RiArchiveRecord(), everyone else uses
        write().
        """
        self.out.write(data)
        

###################### Standard error handlers #######################

def RiErrorIgnore(code, severity, message):
    """Standard error handler.

    Ignores error messages."""
    
    pass

def RiErrorPrint(code, severity, message):
    """Standard error handler.

    Prints the message to stderr."""

    if severity==RIE_WARNING:
        print >> sys.stderr, "WARNING:",
    elif severity==RIE_ERROR or severity==RIE_SEVERE:
        print >> sys.stderr, "ERROR (%d):"%(code),        
    print >> sys.stderr, message

def RiErrorAbort(code, severity, message):
    """Standard error handler.

    Prints the message to stderr and aborts if it was an error."""

    RiErrorPrint(code, severity, message)
    if severity>=RIE_ERROR:
        sys.exit(1)


class RIException(Exception):
    """RenderMan Interface exception

    This exception is thrown by the error handler RiErrorException()."""
    pass

def RiErrorException(code, severity, message):
    """This error handler raises an exception when an error occurs.

    If the "error" is only an info or warning message the message is
    printed to stderr, otherwise the exception RIException is thrown.
    The actual error message is given as an argument to the constructor of
    RIException (the line with the file name, line number and offending
    Ri call is removed. You will have that information in the Traceback).
    """
    if severity<RIE_ERROR:
        RiErrorPrint(code, severity, message)
    else:
        if message[:7]=="In file":
            n=message.find("\n")
            message=message[n+1:]
        raise RIException(message)

########################### Functions ################################

# RiErrorHandler
def RiErrorHandler(handler):
    """Install a new error handler.

    The handler takes three arguments: code, severity, message.
    Besides the three standard error handler RiErrorIgnore, RiErrorPrint
    and RiErrorAbort there's an additional error handler available called
    RiErrorException. Whenever an error occurs RiErrorException raises
    the exception RIException.

    If you use one of the standard error handlers the corresponding RIB
    request is written to the output. If you supply RiErrorException or
    your own handler then the handler is installed but no output is
    written to the output stream.

    The last error code is always stored in the variable RiLastError.
    Note: If you import the module with "from ri import *" you have to
    import it with "import ri" as well and you must access RiLastError
    via "ri.RiLastError" otherwise the variable will always be 0.

    Example: RiErrorHandler(RiErrorAbort)
    """

    global _errorhandler

    _errorhandler = handler

    if handler==RiErrorIgnore:
        _ribout.write('ErrorHandler "ignore"\n')
    elif handler==RiErrorPrint:
        _ribout.write('ErrorHandler "print"\n')
    elif handler==RiErrorAbort:
        _ribout.write('ErrorHandler "abort"\n')


# RiBegin
def RiBegin(name):
    """Starts the main block using a particular rendering method.
    
    The default renderer is selected by passing RI_NULL as name.
    Here this means the output is written to stdout.
    If the name has the extension ".rib" then the output is written into
    a file with that name. Otherwise the name is supposed to be an
    external renderer (e.g. "rendrib" (BMRT), "rgl" (BMRT), "aqsis" (Aqsis),
    "renderdl" (3Delight),...) which is started and fed with the data.

    Example: RiBegin(RI_NULL)
             ...
             RiEnd()
    """
    global _ribout, _colorsamples, _lighthandle, _errorhandler
    global _insideframe, _insideworld, _insideobject, _insidesolid
    global _insidemotion, _declarations

    _create_new_context()

    # Determine where the output should be directed to...
    if name==RI_NULL or name=="":
        # -> stdout
        outstream = sys.stdout
    else:
        root, ext = os.path.splitext(name)
        ext=ext.lower()
        if ext==".rib":
            # -> file (rib)
            outstream = open(name,"w")
        elif ext==".gz":
            outstream = gzip.open(name,"wb")
        else:
            # -> pipe
            outstream = os.popen(name,"w")

    _ribout = RIBStream(outstream)

    # Initialize internal variables
    _colorsamples = 3
    _lighthandle  = 0
    _errorhandler = RiErrorPrint
    _insideframe  = 0
    _insideworld  = 0
    _insideobject = 0
    _insidesolid  = 0
    _insidemotion = 0
    _init_declarations()


# RiEnd
def RiEnd():
    """Terminates the main block.
    """
    global _ribout

    _ribout.flush()
    
    if _ribout != sys.stdout:
        _ribout.close()
        _ribout = sys.stdout

    _destroy_context()

# RiWorldBegin
def RiWorldBegin():
    """Start the world block.

    Example: RiWorldBegin()
             ...
             RiWorldEnd()
    """

    global _insideworld

    if _insideworld:
        _error(RIE_ILLSTATE, RIE_ERROR, "World blocks cannot be nested.")
    
    _ribout.write("WorldBegin\n")
    _insideworld = 1

# RiWorldEnd
def RiWorldEnd():
    """Terminates the world block."""

    global _insideworld
    
    _ribout.write("WorldEnd\n")
    _insideworld = 0

# RiOption
def RiOption(name, *paramlist, **keyparams):
    """Set an implementation-specific option.

    Example: RiOption("searchpath", "shader","~/shaders:&")
    """
    global _ribout

    # cgkit specific options?
    if name==RI_RIBOUTPUT:
        keyparams = _paramlist2lut(paramlist, keyparams)
        if keyparams.get(RI_VERSION, None)==0:
            # Disable the "version" call in the RIB stream...
            if hasattr(_ribout, "output_version"):
                _ribout.output_version = 0
        return
            
    _ribout.write('Option "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiAttribute
def RiAttribute(name, *paramlist, **keyparams):
    """Set an implementation-specific attribute.

    Example: RiAttribute("displacementbound", "sphere", 0.5)
    """
    
    _ribout.write('Attribute "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiAttributeBegin
def RiAttributeBegin():
    """Push the current set of attributes onto the attribute stack.

    Example: RiAttributeBegin()
             ...
             RiAttributeEnd()
    """
    
    _ribout.write("AttributeBegin\n")

# RiAttributeEnd
def RiAttributeEnd():
    """Pops the current set of attributes from the attribute stack."""

    _ribout.write("AttributeEnd\n")

# RiTransformBegin
def RiTransformBegin():
    """Push the current transformation on the transformation stack.

    Example: RiTransformBegin()
             ...
             RiTransformEnd()
    """
    
    _ribout.write("TransformBegin\n")

# RiTransformEnd
def RiTransformEnd():
    """Pop the current transformation from the stack."""

    _ribout.write("TransformEnd\n")

# RiFrameBegin
def RiFrameBegin(number):
    """Start a new frame.

    Example: RiFrameBegin(1)
             ...
             RiFrameEnd()
    """

    global _insideframe

    if _insideframe:
        _error(RIE_ILLSTATE, RIE_ERROR, "Frame blocks cannot be nested.")
            
    _ribout.write("FrameBegin %d\n"%number)
    _insideframe = 1
    

# RiFrameEnd
def RiFrameEnd():
    """Terminates a frame."""
    
    global _insideframe
    
    _ribout.write("FrameEnd\n")
    _insideframe = 0

# RiHider
def RiHider(type, *paramlist, **keyparams):
    """Choose a hidden-surface elimination technique.

    Example: RiHider(RI_HIDDEN)  (default)
    """
    
    if type==RI_NULL: type="null"
    _ribout.write('Hider "'+type+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiSphere
def RiSphere(radius,zmin,zmax,thetamax,*paramlist, **keyparams):
    """Create a sphere.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiSphere(1.0, -1.0, 1.0, 360)
    """

    _ribout.write('Sphere %s %s %s %s'%(radius, zmin, zmax, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiCone
def RiCone(height, radius, thetamax, *paramlist, **keyparams):
    """Create a cone (along the z axis).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiCone(1.5, 0.7, 360)
    """

    _ribout.write('Cone %s %s %s'%(height, radius, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiDisk
def RiDisk(height, radius, thetamax, *paramlist, **keyparams):
    """Create a disk (parallel to the XY plane).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiDisk(0.0, 1.0, 360)"""

    _ribout.write('Disk %s %s %s'%(height, radius, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiCylinder
def RiCylinder(radius,zmin,zmax,thetamax,*paramlist, **keyparams):
    """Create a cylinder (along the z axis).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiCylinder(1.5, 0.0, 1.0, 360)
    """

    _ribout.write('Cylinder %s %s %s %s'%(radius, zmin, zmax, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiTorus
def RiTorus(major, minor, phimin, phimax, thetamax, *paramlist, **keyparams):
    """Create a torus (with the z axis as symmetry axis).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiTorus(1.5, 0.1, 0, 360, 360)
    """

    _ribout.write('Torus %s %s %s %s %s'%(major, minor, phimin, phimax, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiHyperboloid
def RiHyperboloid(point1, point2, thetamax, *paramlist, **keyparams):
    """Create a hyperboloid (with the z axis as symmetry axis).

    Example: RiHyperboloid([1,0,0],[1,1,1],360)
    """

    p1 = _seq2list(point1, 3)
    p2 = _seq2list(point2, 3)
    _ribout.write('Hyperboloid '+p1[1:-1]+' '+p2[1:-1]+' '+`thetamax`+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiParaboloid
def RiParaboloid(rmax, zmin, zmax, thetamax, *paramlist, **keyparams):
    """Create a paraboloid (with the z axis as symmetry axis).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4

    Example: RiParaboloid(1.0, 0.0, 1.0, 360)
    """

    _ribout.write('Paraboloid %s %s %s %s'%(rmax, zmin, zmax, thetamax)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiPolygon
def RiPolygon(*paramlist, **keyparams):
    """Create a planar and convex polygon.

    The parameter list must include at least position ("P") information.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #vertices
    uniform:  1              vertex:  #vertices

    Example: RiPolygon(P=[0,1,0, 0,1,1, 0,0,1, 0,0,0])
    """

    _ribout.write('Polygon'+_paramlist2string(paramlist, keyparams)+"\n")

# RiGeneralPolygon
def RiGeneralPolygon(nverts, *paramlist, **keyparams):
    """Create a general planar concave polygon with holes.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #total vertices
    uniform:  1              vertex:  #total vertices

    Example: RiGeneralPolygon([4,3], P=[0,0,0, 0,1,0, 0,1,1, 0,0,1, \\
                                        0,0.25,0.5, 0,0.75,0.75, 0,0.75,0.25])
    """

    _ribout.write('GeneralPolygon '+_seq2list(nverts)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiPointsPolygons
def RiPointsPolygons(nverts, vertids, *paramlist, **keyparams):
    """Create a polyhedron made of planar convex polygons that share vertices.

    nverts:  An array with the number of vertices in each polygon
    vertids: The vertex indices of the polygon vertices (0-based)
    The vertices themselves are stored in the parameter list (parameter "P").

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #vertices (*)
    uniform:  #polygons      vertex:  #vertices (*)

    (*) max(vertids)+1
    """

    _ribout.write('PointsPolygons '+_seq2list(nverts)+' '+ \
                 _seq2list(vertids)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiPointsGeneralPolygons
def RiPointsGeneralPolygons(nloops, nverts, vertids, *paramlist, **keyparams):
    """Create a polyhedron made of general planar concave polygons.

    nloops:  The number of loops for each polygon
    nverts:  The number of vertices in each loop
    vertids: The vertex indices of the loop vertices (0-based)
    The vertices themselves are stored in the parameter list (parameter "P").

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #vertices (*)
    uniform:  #polygons      vertex:  #vertices (*)

    (*) max(vertids)+1
    """

    _ribout.write('PointsGeneralPolygons '+_seq2list(nloops)+' '+ \
                 _seq2list(nverts)+' '+_seq2list(vertids)+ \
                 _paramlist2string(paramlist, keyparams)+"\n")


# Predefined basis matrices
RiHermiteBasis = "hermite"
RiCatmullRomBasis = "catmull-rom"
RiBezierBasis = "bezier"
RiBSplineBasis = "b-spline"
RiPowerBasis = "power"

# RiBasis
def RiBasis(ubasis, ustep, vbasis, vstep):
    """Set the current basis for the u and v direction.

    ubasis/vbasis can either be one of the predefined basis matrices
    RiHermiteBasis, RiCatmullRomBasis, RiBezierBasis, RiBSplineBasis,
    RiPowerBasis or it can be a user defined matrix.

    For the predefined matrices there are also predefined variables
    which can be used for the step parameters:
    RI_HERMITESTEP, RI_CATMULLROMSTEP, RI_BEZIERSTEP, RI_BSPLINESTEP,
    RI_POWERSTEP.

    Example: RiBasis(RiBezierBasis, RI_BEZIERSTEP,
                     RiHermiteBasis, RI_HERMITESTEP)
    """

    if type(ubasis)==types.StringType:
        ubasis = '"'+ubasis+'"'
    else:
        ubasis = _seq2list(ubasis, 16)
        
    if type(vbasis)==types.StringType:
        vbasis = '"'+vbasis+'"'
    else:
        vbasis = _seq2list(vbasis, 16)
        
    _ribout.write('Basis '+ubasis+' '+str(ustep)+' '+vbasis+' '+str(vstep)+"\n")

# RiPatch
def RiPatch(type, *paramlist, **keyparams):
    """RiPatch(type, paramlist)

    type is one of RI_BILINEAR (4 vertices) or RI_BICUBIC (16 vertices).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: 4
    uniform:  1              vertex:  4/16 (depends on type)

    Example: RiPatch(RI_BILINEAR, P=[0,0,0, 1,0,0, 0,1,0, 1,1,0])
    """

    _ribout.write('Patch "'+type+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiPatchMesh
def RiPatchMesh(type, nu, uwrap, nv, vwrap, *paramlist, **keyparams):
    """Create a mesh made of patches.

    type is one of RI_BILINEAR or RI_BICUBIC.
    uwrap/vwrap can be RI_PERIODIC or RI_NONPERIODIC.
    The number of control points is nu*nv.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #patch corners (depends on uwrap/vwrap)
    uniform:  #patches       vertex:  nu*nv (same as "P")

    """

    _ribout.write('PatchMesh "'+type+'" '+str(nu)+' "'+uwrap+'" '+\
                 str(nv)+' "'+vwrap+'"'+\
                 _paramlist2string(paramlist, keyparams)+"\n")
    

# RiNuPatch
def RiNuPatch(nu, uorder, uknot, umin, umax, nv, vorder, vknot, vmin, vmax, *paramlist, **keyparams):
    """Create a NURBS patch.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #segment corners
    uniform:  #segments      vertex:  nu*nv
    """

    _ribout.write('NuPatch '+str(nu)+" "+str(uorder)+' '+_seq2list(uknot)+" "+ \
                 str(umin)+" "+str(umax)+" "+ \
                str(nv)+" "+str(vorder)+' '+_seq2list(vknot)+" "+ \
                 str(vmin)+" "+str(vmax)+_paramlist2string(paramlist, keyparams)+"\n")

# RiTrimCurve
def RiTrimCurve(ncurves, order, knot, min, max, n, u, v, w):
    """Set the current trim curve.
    """

    _ribout.write('TrimCurve '+_seq2list(ncurves)+' '+\
                 _seq2list(order)+' '+_seq2list(knot)+' '+\
                 _seq2list(min)+' '+_seq2list(max)+' '+_seq2list(n)+' '+ \
                 _seq2list(u)+' '+ \
                 _seq2list(v)+' '+ \
                 _seq2list(w)+'\n')

# RiPoints
def RiPoints(*paramlist, **keyparams):
    """Create individual points.

    The size of the points can be either set with the primitive variable
    RI_WIDTH (one float per point) or RI_CONSTANTWIDTH (one float for all
    points).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #points
    uniform:  1              vertex:  #points    
    """

    _ribout.write('Points'+_paramlist2string(paramlist, keyparams)+"\n")

# RiCurves
def RiCurves(type, nvertices, wrap, *paramlist, **keyparams):
    """Create a number of curve primitives.

    type is either RI_LINEAR or RI_CUBIC.
    nvertices is an array with the number of vertices in each curve.
    wrap is either RI_PERIODIC or RI_NONPERIODIC.
    The width of the curves can be specified with the parameter
    RI_WIDTH (varying float) or RI_CONSTANTWIDTH (constant float).

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #segments (depends on type and wrap)
    uniform:  #curves        vertex:  #points

    Example: RiCurves(RI_CUBIC, [4], RI_NONPERIODIC,
                      P=[0,0,0, -1,-0.5,1, 2,0.5,1, 1,0,-1],
                      width=[0.1, 0.04])
    """

    _ribout.write('Curves "'+type+'" '+_seq2list(nvertices)+' "'+wrap+'"'+
                  _paramlist2string(paramlist, keyparams)+'\n')

# RiSubdivisionMesh
def RiSubdivisionMesh(scheme, nverts, vertids, tags, nargs, intargs, floatargs, *paramlist, **keyparams):
    """Create a subdivision surface.

    The only standard scheme is currently "catmull-clark".
    nverts:  The number of vertices in each face
    vertids: The vertex indices of the face vertices (0-based)
    tags: A string array of tag names.
    nargs: The number of int and float args for each tag.
    intargs: The integer arguments.
    floatargs: The float arguments.
    The vertices themselves are stored in the parameter list (parameter "P").
    

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: #vertices (*)
    uniform:  #faces         vertex:  #vertices (*)

    (*) max(vertids)+1
    """

    if len(tags)==0:
        _ribout.write('SubdivisionMesh "'+scheme+'" '+_seq2list(nverts)+' '+ \
                 _seq2list(vertids)+' '+ \
                 _paramlist2string(paramlist, keyparams)+"\n")
    else:
        _ribout.write('SubdivisionMesh "'+scheme+'" '+_seq2list(nverts)+' '+ \
                 _seq2list(vertids)+' '+_seq2list(tags)+' '+ \
                 _seq2list(nargs)+' '+_seq2list(intargs)+' '+ \
                 _seq2list(floatargs)+' '+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiBlobby
def RiBlobby(nleaf, code, floats, strings, *paramlist, **keyparams):
    """Create a blobby surface.

    Number of array elements for primitive variables:
    -------------------------------------------------
    constant: 1              varying: nleaf
    uniform:  1              vertex:  nleaf

    Example: RiBlobby(2, [1001,0, 1003,0,16, 0,2,0,1],
                      [1.5,0,0,0, 0,1.5,0,0, 0,0,1.5,0, 0,0,-.1,1,
                      0.4, 0.01,0.3, 0.08], ["flat.zfile"])
    """

    _ribout.write('Blobby '+str(nleaf)+' '+_seq2list(code)+' '+_seq2list(floats)+
                  ' '+_seq2list(strings)+
                  _paramlist2string(paramlist, keyparams)+'\n')

# RiColorSamples
def RiColorSamples(nRGB, RGBn):
    """Redefine the number of color components to be used for specifying colors.

    nRGB is a n x 3 matrix that can be used to transform the n component color
    to a RGB color (n -> RGB).
    RGBn is just the opposite, its a 3 x n matrix that's used to transform
    a RGB color to a n component color (RGB -> n).
    Thus, the new number of color components is len(matrix)/3 (matrix is
    either nRGB or RGBn).

    Example: RiColorSamples([0.3,0.3,0.3], [1,1,1])
    """
    global _colorsamples

    if len(nRGB)!=len(RGBn):
        _error(RIE_CONSISTENCY, RIE_ERROR,
               "The color transformation matrices must have the same number of values.")

    if len(nRGB)%3!=0 or len(nRGB)==0:
        _error(RIE_CONSISTENCY, RIE_ERROR,
               "The number of values in the transformation matrices must be a multiple of 3.")
        
    _colorsamples = len(_flatten(nRGB))/3
    _ribout.write('ColorSamples '+_seq2list(nRGB)+' '+_seq2list(RGBn)+'\n')

# RiColor
def RiColor(Cs):
    """Set the current color.

    Cs must be a sequence of at least N values where N is the number of
    color samples (set by RiColorSamples(), default is 3).

    Example: RiColor([0.2,0.5,0.2])
    """

    col=_seq2col(Cs)
    _ribout.write("Color "+col+"\n")

# RiOpacity
def RiOpacity(Os):
    """Set the current opacity.

    Os must be a sequence of at least N values where N is the number
    of color samples (set by RiColorSamples(), default is 3). The
    opacity values must lie in the range from 0 to 1 (where 0 means
    completely transparent and 1 means completely opaque).

    Example: RiOpacity([0,0,1])
    """

    col=_seq2col(Os)
    _ribout.write("Opacity "+col+"\n")

# RiShadingRate
def RiShadingRate(size):
    """Set the current shading rate to an area of size pixels.

    Example: RiShadingRate(1.0)
    """

    _ribout.write("ShadingRate %s\n"%size)

# RiShadingInterpolation
def RiShadingInterpolation(type):
    """Specify how shading samples are interpolated.

    type can be RI_CONSTANT or RI_SMOOTH.

    Example: RiShadingInterpolation(RI_SMOOTH)"""

    _ribout.write('ShadingInterpolation "'+type+'"\n')

# RiSurface
def RiSurface(name, *paramlist, **keyparams):
    """Set the current surface shader.

    Example: RiSurface("plastic", Kd=0.7, Ks=0.3)"""

    _ribout.write('Surface "'+name+'"'+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiInterior
def RiInterior(name, *paramlist, **keyparams):
    """Set the current interior volume shader.

    Example: RiInterior("water")
    """

    _ribout.write('Interior "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiExterior
def RiExterior(name, *paramlist, **keyparams):
    """Set the current exterior volume shader.

    Example: RiExterior("fog")
    """

    _ribout.write('Exterior "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiAtmosphere
def RiAtmosphere(name, *paramlist, **keyparams):
    """Set the current atmosphere shader.

    If name is RI_NULL then no atmosphere shader is used.

    Example: RiAtmosphere("fog")
    """

    if name==RI_NULL:
        _ribout.write('Atmosphere\n')
    else:
        _ribout.write('Atmosphere "'+name+'"'+ \
                      _paramlist2string(paramlist, keyparams)+"\n")

# RiDisplacement
def RiDisplacement(name, *paramlist, **keyparams):
    """Set the current displacement shader.

    Example: RiDisplacement("dented", km=1.5)
    """

    _ribout.write('Displacement "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiImager
def RiImager(name, *paramlist, **keyparams):
    """Set an imager shader.

    if name is RI_NULL, no imager shader is used.

    Example: RiImager("background", "color bgcolor", [0.3,0.3,0.9])
    """

    if name==RI_NULL:
        _ribout.write('Imager\n')
    else:
        _ribout.write('Imager "'+name+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiClipping
def RiClipping(near, far):
    """Sets the near and the far clipping plane along the direction of view.

    near and far must be positive values in the range from RI_EPSILON to
    RI_INFINITY.

    Example: RiClipping(0.1, 100)
    """

    _ribout.write("Clipping %s %s\n"%(near,far))

# RiClippingPlane
def RiClippingPlane(x, y, z, nx, ny, nz):
    """Adds a new clipping plane, defined by a surface point and its normal.

    All the geometry in positive normal direction is clipped.

    Example: RiClippingPlane(0,0,0, 0,0,-1) clips everything below the XY plane
    """

    _ribout.write('ClippingPlane %s %s %s %s %s %s\n'%(x,y,z,nx,ny,nz))

# RiDisplay
def RiDisplay(name,type,mode, *paramlist, **keyparams):
    """Specify the destination and type of the output.

    Example: RiDisplay("frame0001.tif", RI_FILE, RI_RGB)
             RiDisplay("myimage.tif", RI_FRAMEBUFFER, RI_RGB)
    """

    _ribout.write('Display "'+name+'" "'+type+'" "'+mode+'"'+ \
                 _paramlist2string(paramlist, keyparams)+"\n")

# RiFormat
def RiFormat(xres, yres, aspect):
    """Set the resolution of the output image and the aspect ratio of a pixel.

    Example: RiFormat(720,576,1)"""

    _ribout.write('Format %s %s %s\n'%(xres, yres, aspect))

# RiFrameAspectRatio
def RiFrameAspectRatio(frameratio):
    """Set the ratio between width and height of the image.

    Example: RiFrameAspectRatio(4.0/3)
    """

    _ribout.write('FrameAspectRatio %s\n'%frameratio)

# RiGeometricApproximation
def RiGeometricApproximation(type, value):
    """Sets parameters for approximating surfaces.

    Example: RiGeometricApproximation(RI_FLATNESS, 0.5)  (default value)
    """

    _ribout.write('GeometricApproximation "'+type+'" '+str(value)+"\n")


# RiProjection
def RiProjection(name, *paramlist, **keyparams):
    """Specify a projection method.

    The standard projections are RI_PERSPECTIVE and RI_ORTHOGRAPHIC.
    The perspective projection takes one optional parameter, RI_FOV.

    Example: RiProjection(RI_PERSPECTIVE, fov=45)
    """

    if name==RI_NULL:
        _ribout.write('Projection\n')
    else:
        _ribout.write('Projection "'+name+'"'+ \
                      _paramlist2string(paramlist, keyparams)+"\n")

# RiScreenWindow
def RiScreenWindow(left, right, bottom, top):
    """Specify the extents of the output image on the image plane.

    Example: RiScreenWindow(-1,1,-1,1)
    """

    _ribout.write('ScreenWindow %s %s %s %s\n'%(left, right, bottom, top))

# RiCropWindow
def RiCropWindow(left, right, bottom, top):
    """Specify a subwindow to render.

    The values each lie between 0 and 1.

    Example: RiCropWindow(0.0, 1.0 , 0.0, 1.0)  (renders the entire frame)
             RiCropWindow(0.5, 1.0 , 0.0, 0.5)  (renders the top right quarter)
    """

    _ribout.write('CropWindow %s %s %s %s\n'%(left, right, bottom, top))

# RiPixelSamples
def RiPixelSamples(xsamples, ysamples):
    """Set the sampling rate in horizontal and vertical direction.

    Example: RiPixelSamples(2,2)"""

    _ribout.write("PixelSamples %s %s\n"%(max(1,xsamples), max(1,ysamples)))

# RiPixelVariance
def RiPixelVariance(variance):
    """Limit the acceptable variance in the output value of pixels.

    Example: RiPixelVariance(0.01)"""

    _ribout.write('PixelVariance %s\n'%variance)

# Predefined filter functions:
RiGaussianFilter   = "gaussian"
RiBoxFilter        = "box"
RiTriangleFilter   = "triangle"
RiSincFilter       = "sinc"
RiCatmullRomFilter = "catmull-rom"

# RiPixelFilter
def RiPixelFilter(function, xwidth, ywidth):
    """Set a pixel filter function and its width in pixels.

    Note: Here you can only use one of the predefined filter functions:
    RiGaussianFilter, RiBoxFilter, RiTriangleFilter, RiSincFilter and
    RiCatmullRomFilter.
    Passing a callable as filter function will issue a warning and ignore
    the call.

    Example: RiPixelFilter(RiGaussianFilter, 2.0, 1.0)"""
    
    if callable(function):
        _error(RIE_INCAPABLE, RIE_WARNING, "Only the standard filters can be stored in a RIB stream.")
        return

    _ribout.write('PixelFilter "'+function+'" '+str(xwidth)+' '+str(ywidth)+'\n')

# RiExposure
def RiExposure(gain, gamma):
    """Sets the parameters for the output color transformation.

    The transformation is color_out = (color_in*gain)^(1/gamma)

    Example: RiExposure(1.3, 2.2)
    """

    _ribout.write("Exposure %s %s\n"%(gain, gamma))

# RiQuantize
def RiQuantize(type, one, min, max, ditheramplitude):
    """Set the quantization parameters for colors and depth.

    Example: RiQuantize(RI_RGBA, 2048, -1024, 3071, 1.0)
    """

    _ribout.write('Quantize "%s" %s %s %s %s\n'%(type, one, min, max, ditheramplitude))
    

# RiDepthOfField
def RiDepthOfField(fstop, focallength, focaldistance):
    """Set depth of field parameters.

    If fstop is RI_INFINITY depth of field is turned off.

    Example: RiDepthOfField(22,45,1200)
    """

    if fstop==RI_INFINITY:
        _ribout.write('DepthOfField\n')
    else:
        _ribout.write('DepthOfField %s %s %s\n'%(fstop, focallength, focaldistance))

# RiMotionBegin
def RiMotionBegin(*times):
    """Start the definition of a moving primitive.

    You can specify the time values directly or inside a sequence,
    for example, RiMotionBegin(0,1) or RiMotionBegin([0,1]).

    Example: RiMotionBegin(0.0, 1.0)
             RiTranslate(1.0, 0.0, 0.0)
             RiTranslate(1.0, 2.0, 0.0)
             RiMotionEnd()
    """

    global _insidemotion

    if _insidemotion:
        _error(RIE_ILLSTATE, RIE_ERROR, "Motion blocks cannot be nested.")

    _ribout.write('MotionBegin '+_seq2list(times)+'\n')
    _insidemotion = 1

# RiMotionEnd
def RiMotionEnd():
    "Terminates the definition of a moving primitive."

    global _insidemotion

    _ribout.write('MotionEnd\n')
    _insidemotion = 0

# RiShutter
def RiShutter(opentime, closetime):
    """Set the times at which the shutter opens and closes.

    Example: RiShutter(0.1, 0.9)
    """

    _ribout.write('Shutter %s %s\n'%(opentime, closetime))

# RiTranslate
def RiTranslate(*translation):
    """Concatenate a translation onto the current transformation.

    The translation is either given as 3 scalars or a sequence of
    3 scalars.

    Example: RiTranslate(1.2, 4.3, -0.5)
             or
             RiTranslate( (1.2, 4.3, -0.5) )
    """

    # Argument = sequence?
    if len(translation)==1:
        s=_seq2list(translation,3)
        _ribout.write('Translate '+s[1:-1]+"\n")
    # Argument = 3 scalars?
    elif len(translation)==3:
        dx,dy,dz=translation
        _ribout.write('Translate %s %s %s\n'%(dx,dy,dz))
    # Invalid argument size
    else:
        raise TypeError, "RiTranslate() only takes a sequence or three scalars as arguments"

# RiRotate
def RiRotate(angle, *axis):
    """Rotate about angle degrees about the given axis.

    The axis is either given as 3 scalars or a sequence of 3 scalars.

    Example: RiRotate(90, 1,0,0)
    """

    # Argument = sequence?
    if len(axis)==1:
        s=_seq2list(axis,3)
        _ribout.write('Rotate %s %s\n'%(angle, s[1:-1]))
    # Argument = 3 scalars?
    elif len(axis)==3:
        ax,ay,az=axis
        _ribout.write('Rotate %s %s %s %s\n'%(angle, ax, ay, az))
    # Invalid argument size
    else:
        raise TypeError, "RiRotate() only takes 2 or 4 arguments ("+`len(axis)+1`+" given)"


# RiScale
def RiScale(*scaling):
    """Concatenate a scaling onto the current transformation.

    The scaling is either given as 3 scalars or a sequence of 3 scalars.

    Example: RiScale(2,2,2)"""

    # Argument = sequence?
    if len(scaling)==1:
        s=_seq2list(scaling,3)
        _ribout.write('Scale '+s[1:-1]+"\n")
    # Argument = 3 scalars?
    elif len(scaling)==3:
        sx,sy,sz=scaling
        _ribout.write('Scale %s %s %s\n'%(sx, sy, sz))
    # Invalid argument size
    else:
        raise TypeError, "RiScale() only takes a sequence or three scalars as arguments"


# RiSkew
def RiSkew(angle, *vecs):
    """Concatenate a skew onto the current transformation.

    angle is given in degrees.
    The two vectors are each given as 3 scalars or a sequence
    of 3 scalars.

    Example: RiSkew(45, 0,1,0, 1,0,0)
    """

    # Argument = two sequences?
    if len(vecs)==2:
        s1=_seq2list(vecs[0],3)
        s2=_seq2list(vecs[1],3)
        _ribout.write('Skew '+str(angle)+" "+s1[1:-1]+" "+s2[1:-1]+"\n")
    # Argument = 6 scalars?
    elif len(vecs)==6:
        dx1,dy1,dz1,dx2,dy2,dz2=vecs
        _ribout.write('Skew %s %s %s %s %s %s %s\n'%(angle, dx1, dy1, dz1, dx2, dy2, dz2))
    # Invalid argument size
    else:
        raise TypeError, "RiSkew() only takes 3 or 7 arguments ("+`len(vecs)+1`+" given)"


# RiPerspective
def RiPerspective(fov):
    """Concatenate a perspective transformation onto the current transformation.
    
    Example: RiPerspective(45)"""

    _ribout.write('Perspective %s\n'%fov)

# RiIdentity
def RiIdentity():
    """Set the current transformation to the identity.

    Example: RiIdentity()
    """

    _ribout.write('Identity\n')

# RiConcatTransform
def RiConcatTransform(transform):
    """Concatenate a transformation onto the current transformation.

    transform must be a sequence that evaluates to 16 floating point
    values (4x4 matrix).

    Example: RiConcatTransform([2,0,0,0, 0,2,0,0, 0,0,2,0, 0,0,0,1])
             RiConcatTransform([[2,0,0,0], [0,2,0,0], [0,0,2,0], [0,0,0,1]])
    """

    _ribout.write('ConcatTransform '+_seq2list(transform,16)+"\n")

# RiTransform
def RiTransform(transform):
    """Set the current transformation.

    transform must be a sequence that evaluates to 16 floating point
    values (4x4 matrix).

    Example: RiTransform([2,0,0,0, 0,2,0,0, 0,0,2,0, 0,0,0,1])
             RiTransform([[2,0,0,0], [0,2,0,0], [0,0,2,0], [0,0,0,1]])
    """

    _ribout.write('Transform '+_seq2list(transform,16)+"\n")

# RiSides
def RiSides(nsides):
    """Specify the number of visible sides of subsequent surfaces.

    Example: RiSides(1)"""

    if nsides!=1 and nsides!=2:
        _error(RIE_RANGE, RIE_ERROR, "The number of sides (nsides) must be either 1 or 2.")

    _ribout.write('Sides %s\n'%nsides)

# RiOrientation
def RiOrientation(orientation):
    """Set the orientation of subsequent surfaces.

    orientation is either RI_OUTSIDE, RI_INSIDE, RI_LH (left handed)
    or RI_RH (right handed).
    """

    _ribout.write('Orientation "'+orientation+'"\n')

# RiReverseOrientation
def RiReverseOrientation():
    """Causes the current orientation to be toggled.

    Example: RiReverseOrientation()
    """

    _ribout.write('ReverseOrientation\n')

# RiMatte
def RiMatte(onoff):
    """Indicates whether subsequent primitives are matte objects.

    Example: RiMatte(RI_TRUE)
    """
    if onoff:
        _ribout.write('Matte 1\n')
    else:
        _ribout.write('Matte 0\n')

# RiLightSource
def RiLightSource(name, *paramlist, **keyparams):
    """Add another light source and return its light handle.

    name is the name of the light source shader. You can set a user defined
    string handle via the RI_HANDLEID parameter.

    Example: light1 = RiLightSource("distantlight", intensity=1.5)
    """
    global _lighthandle

    paramlist = _merge_paramlist(paramlist, keyparams)
    lshandle = None
    for i in range(0, len(paramlist), 2):
        token = paramlist[i]
        if token==RI_HANDLEID:
            lshandle = str(paramlist[i+1])
            paramlist = paramlist[:i]+paramlist[i+2:]
            break

    # Check if the user provided a handle id...
    if lshandle is None:
        _lighthandle += 1
        lshandle = _lighthandle
        _ribout.write('LightSource "%s" %d%s\n'%(name, lshandle, _paramlist2string(paramlist, {})))
    else:
        _ribout.write('LightSource "%s" "%s"%s\n'%(name, lshandle, _paramlist2string(paramlist, {})))
        
    return lshandle

# RiIlluminate
def RiIlluminate(light, onoff):
    """Activate or deactive a light source.

    Example: RiIlluminate(lgt, RI_TRUE)
    """

    if type(light)==str or type(light)==unicode:
        _ribout.write('Illuminate "%s" %d\n'%(light, onoff))
    else:
        _ribout.write('Illuminate %d %d\n'%(light, onoff))
        

# RiAreaLightSource
def RiAreaLightSource(name, *paramlist, **keyparams):
    """Start the definition of an area light and return the light handle.

    You can set a user defined string handle via the RI_HANDLEID parameter.
    
    Example: RiAttributeBegin()
             area1 = RiAreaLightSource("arealight", intensity=0.75)
             ....
             RiAttributeEnd()
             RiIlluminate(area1, RI_TRUE)
    """
    global _lighthandle

    # Check if the user provided a handle id...
    if RI_HANDLEID in keyparams:
        lshandle = str(keyparams[RI_HANDLEID])
        del keyparams[RI_HANDLEID]
        _ribout.write('AreaLightSource "%s" "%s"%s\n'%(name, lshandle, _paramlist2string((), keyparams)))
    else:
        _lighthandle+=1
        lshandle = _lighthandle
        _ribout.write('AreaLightSource "%s" %d%s\n'%(name, lshandle, _paramlist2string((), keyparams)))

    return lshandle

# RiDeclare
def RiDeclare(name, declaration):
    """Declare the name and type of a variable.

    The syntax of the declaration is:  [class] [type] ['['n']']

    class ::= constant | uniform | varying | vertex
    type  ::= float | integer | string | color | point | vector | normal |
              matrix | hpoint
    
    Example: RiDeclare("foo","uniform float")
             RiDeclare("bar","constant integer [4]")
             RiDeclare("mycolor", "varying color")
    """

    global _declarations

    if declaration==RI_NULL:
        declaration=""
        
    _ribout.write('Declare "'+name+'" "'+declaration+'"\n')
    _declarations[name]=declaration
    return name

# RiArchiveRecord
def RiArchiveRecord(type, format, *args):
    """Output a user data record.

    type is one of RI_COMMENT, RI_STRUCTURE or RI_VERBATIM.

    For comments and structural hints you can use the special variables
    $CREATOR, $DATE and $USER which will be replaced by their appropriate
    value (program name, date string, user name).

    Example: RiArchiveRecord(RI_COMMENT, "Frame %d", 2)
             RiArchiveRecord(RI_STRUCTURE, "CreationDate $DATE")
    """

    if type!=RI_VERBATIM and format.find("$")!=-1:
        format = format.replace("$DATE", time.ctime())
        format = format.replace("$CREATOR", sys.argv[0])
        try:
            user = getpass.getuser()
        except:
            user = "<unknown>"
        format = format.replace("$USER", user)

    if type==RI_COMMENT:
        outstr = "# "+format%args+"\n"
    elif type==RI_STRUCTURE:
        outstr = "##"+format%args+"\n"
    elif type==RI_VERBATIM:
        outstr = format%args
    else:
        return

    # Use the writeArchiveRecord() if there is any, otherwise use write()
    # (the latter case happens when RiBegin() wasn't called. _ribout is
    # then set to stdout)
    if hasattr(_ribout, "writeArchiveRecord"):
        _ribout.writeArchiveRecord(outstr)
    else:
        _ribout.write(outstr)
    
    
# RiReadArchive
def RiReadArchive(filename, callback=None, *ignore):
    """Include an archive file.

    In this implementation the callback function is not used and can
    be left out.

    RiExample: RiReadArchive("teapot.rib")"""

    _ribout.write('ReadArchive "'+filename+'"\n')


def RiProcDelayedReadArchive(): return "DelayedReadArchive"
def RiProcRunProgram(): return "RunProgram"
def RiProcDynamicLoad(): return "DynamicLoad"
def RiProcFree(data): pass

# RiProcedural
def RiProcedural(data, bound, subdividefunc, freefunc=None):
    """Declare a procedural model.

    subdividefunc and freefunc may either be the standard RenderMan
    procedurals (RiProcDelayedReadArchive, RiProcRunProgram,
    RiProcDynamicLoad and RiProcFree) or Python callables.
    In the former case, data must be a sequence of strings or a single
    string containing the data for the functions. In the latter case,
    data may be any Python object which is just passed on to the
    functions. 
    freefunc is optional and defaults to None.

    Because this module can only produce RIB, a custom subdivide function is
    simply called with a detail value of RI_INFINITY to generate all the
    data at once.
    
    Example: RiProcedural("mymodel.rib", [-1,1,-1,1,-1,1], \\
                          RiProcDelayedReadArchive, RI_NULL)
                          
             RiProcedural(["python teapot.py",""],[0,1,0,1,0,1], \\
                          RiProcRunProgram, RI_NULL)
                          
             RiProcedural(["teapot.so",""],[0,1,0,1,0,1], \\
                          RiProcDynamicLoad, RI_NULL)
    """
    if subdividefunc in [RiProcDelayedReadArchive, RiProcRunProgram, RiProcDynamicLoad]:
        if type(data)==types.StringType:
            data=[data]
        _ribout.write('Procedural "'+subdividefunc()+'" '+_seq2list(data)+ \
                     ' '+_seq2list(bound,6)+"\n")
    else:
        # Call the custom procedure to generate all the data...
        subdividefunc(data, RI_INFINITY)
        if freefunc is not None:
            freefunc(data)

# RiGeometry
def RiGeometry(type, *paramlist, **keyparams):
    """Create an implementation-specific geometric primitive.

    Example: RiGeometry("teapot")
    """

    _ribout.write('Geometry "'+type+'"'+_paramlist2string(paramlist, keyparams)+"\n")

# RiBound
def RiBound(bound):
    """Set the bounding box for subsequent primitives.

    bound must be a sequence of six floating point values specifying
    the extent of the box along each coordinate direction:
    bound = [xmin, xmax, ymin, ymax, zmin, zmax]

    Example: RiBound([-1,1, 0,1, 0.5,0.75])
    """

    _ribout.write('Bound '+_seq2list(bound,6)+'\n')
    
# RiSolidBegin
def RiSolidBegin(type):
    """Start the definition of a solid object.

    type is one of RI_PRIMITIVE, RI_UNION, RI_DIFFERENCE and RI_INTERSECTION.

    Example: RiSolidBegin(RI_INTERSECTION)
             RiSolidBegin(RI_PRIMITIVE)
             ...
             RiSolidEnd()
             RiSolidBegin(RI_PRIMITIVE)
             ...
             RiSolidEnd()
             RiSolidEnd()
    """

    _ribout.write('SolidBegin "'+type+'"\n')

# RiSolidEnd
def RiSolidEnd():
    """Terminate the definition of a solid object."""

    _ribout.write('SolidEnd\n')

# RiObjectBegin
def RiObjectBegin(*paramlist, **keyparams):
    """Start the definition of a retained model and return the object handle.

    You can pass a user defined string handle via the RI_HANDLEID parameter.

    Example: obj1 = RiObjectBegin()
             ...
             RiObjectEnd()
    """
    global _objecthandle, _insideobject

    if _insideobject:
        _error(RIE_ILLSTATE, RIE_ERROR, "Object blocks cannot be nested.")

    keyparams = _paramlist2dict(paramlist, keyparams)

    # Check if the user provided a handle id...
    if RI_HANDLEID in keyparams:
        objhandle = str(keyparams[RI_HANDLEID])
        del keyparams[RI_HANDLEID]
        _ribout.write('ObjectBegin "%s"\n'%objhandle)
    else:
        _objecthandle+=1
        objhandle = _objecthandle
        _ribout.write('ObjectBegin %d\n'%objhandle)
    
    _insideobject=1

    return objhandle

# RiObjectEct
def RiObjectEnd():
    """Terminate the definition of a retained model."""

    global _insideobject
    
    _ribout.write('ObjectEnd\n')
    _insideobject=0

# RiObjectInstance
def RiObjectInstance(handle):
    """Create an instance of a previously defined model.

    Example: RiObjectInstance(obj1)
    """

    if type(handle)==str or type(handle)==unicode:
        _ribout.write('ObjectInstance "%s"\n'%handle)
    else:
        _ribout.write('ObjectInstance %d\n'%handle)

# RiTextureCoordinates
def RiTextureCoordinates(s1, t1, s2, t2, s3, t3, s4, t4):
    """Set the current set of texture coordinates.

    Declares a projection from the unit square [(0,0), (1,0), (0,1), (1,1)]
    in parameter space to quadrilateral [(s1,t1), (s2,t2), (s3,t3), (s4,t4)]
    in texture space.

    Example: RiTextureCoordinates(0.0, 0.0, 2.0, -0.5, -0.5, 1.75, 3.0, 3.0)"""

    _ribout.write('TextureCoordinates %s %s %s %s %s %s %s %s\n'%(s1,t1,s2,t2,s3,t3,s4,t4))

# RiMakeTexture
def RiMakeTexture(picname, texname, swrap, twrap, filterfunc, swidth, twidth, *paramlist, **keyparams):
    """Convert an image file into a texture file.

    swrap and twrap are one of RI_PERIODIC, RI_CLAMP or RI_BLACK.
    filterfunc has to be one of the predefined filter functions:
    RiGaussianFilter, RiBoxFilter, RiTriangleFilter, RiSincFilter or
    RiCatmullRomFilter (otherwise a warning is issued and the call is ignored).
    swidth and twidth define the support of the filter.

    Example: RiMakeTexture("img.tif", "tex.tif", RI_PERIODIC, RI_CLAMP, \\
                           RiGaussianFilter, 2,2)
    """
    
    if callable(filterfunc):
        _error(RIE_INCAPABLE, RIE_WARNING, "Only the standard filters can be stored in a RIB stream.")
        return

    _ribout.write('MakeTexture "'+picname+'" "'+texname+'" "'+swrap+'" "'+
                  twrap+'" "'+filterfunc+'" '+str(swidth)+' '+str(twidth)+
                  _paramlist2string(paramlist, keyparams)+'\n')

# RiMakeLatLongEnvironment
def RiMakeLatLongEnvironment(picname, texname, filterfunc, swidth, twidth, *paramlist, **keyparams):
    """Convert an image file into an environment map.

    filterfunc has to be one of the predefined filter functions:
    RiGaussianFilter, RiBoxFilter, RiTriangleFilter, RiSincFilter or
    RiCatmullRomFilter (otherwise a warning is issued and the call is ignored).
    swidth and twidth define the support of the filter.

    Example: RiMakeLatLongEnvironment("img.tif", "tex.tif",
                                      RiGaussianFilter, 2,2)
    """
    
    if callable(filterfunc):
        _error(RIE_INCAPABLE, RIE_WARNING, "Only the standard filters can be stored in a RIB stream.")
        return

    _ribout.write('MakeLatLongEnvironment "'+picname+'" "'+texname+'" "'+
                  filterfunc+'" '+str(swidth)+' '+str(twidth)+
                  _paramlist2string(paramlist, keyparams)+'\n')

# RiMakeCubeFaceEnvironment
def RiMakeCubeFaceEnvironment(px,nx,py,ny,pz,nz, texname, fov, filterfunc, swidth, twidth, *paramlist, **keyparams):
    """Convert six image files into an environment map.

    The px/nx images are the views in positive/negative x direction.
    fov is the field of view that was used to generate the individual images.
    filterfunc has to be one of the predefined filter functions:
    RiGaussianFilter, RiBoxFilter, RiTriangleFilter, RiSincFilter or
    RiCatmullRomFilter (otherwise a warning is issued and the call is ignored).
    swidth and twidth define the support of the filter.

    Example: RiMakeCubeFaceEnvironment("px.tif","nx.tif","py.tif","ny.tif",
                                       "pz.tif","nz.tif", "tex.tif", 92.0,
                                        RiGaussianFilter, 2,2)
    """
    
    if callable(filterfunc):
        _error(RIE_INCAPABLE, RIE_WARNING, "Only the standard filters can be stored in a RIB stream.")
        return

    _ribout.write('MakeCubeFaceEnvironment "'+px+'" "'+nx+'" "'+
                  py+'" "'+ny+'" "'+pz+'" "'+nz+'" "'+ texname+'" '+
                  str(fov)+' "'+filterfunc+'" '+str(swidth)+' '+str(twidth)+
                  _paramlist2string(paramlist, keyparams)+'\n')

# RiMakeShadow
def RiMakeShadow(picname, shadowname, *paramlist, **keyparams):
    """Transform a depth image into a shadow map.

    Example: RiMakeShadow("depthimg.tif", "shadow.tif")
    """
    
    _ribout.write('MakeShadow "'+picname+'" "'+shadowname+'"'+_paramlist2string(paramlist, keyparams)+'\n')

# RiDetail
def RiDetail(bound):
    """Set the current bounding box.

    bound must be a sequence of six floating point values specifying
    the extent of the box along each coordinate direction:
    bound = [xmin, xmax, ymin, ymax, zmin, zmax]

    Example: RiDetail([10,20,40,70,0,1])
    """

    _ribout.write('Detail '+_seq2list(bound,6)+'\n')

# RiRelativeDetail
def RiRelativeDetail(relativedetail):
    """Set the factor for all level of detail calculations.

    Example: RiRelativeDetail(0.7)"""

    _ribout.write('RelativeDetail %s\n'%relativedetail)

# RiDetailRange
def RiDetailRange(minvisible, lowertransition, uppertransition, maxvisible):
    """Set the current detail range.

    The values of the parameters must satisfy the following ordering:
    minvisible <= lowertransition <= uppertransition <= maxvisible

                lowertransition  uppertransition
 visibility         |____________________|       
    ^               /                    \\
    |              /                      \\
    |_____________/                        \\_____________\\ Level of detail
                 |                          |            /
             minvisible                      maxvisible

    Example: RiDetailRange(0,0,10,20)
    """

    _ribout.write('DetailRange %s %s %s %s\n'%(minvisible, lowertransition, uppertransition, maxvisible))

# RiCoordinateSystem
def RiCoordinateSystem(spacename):
    """Mark the current coordinate system with a name.

    Example: RiCoordinateSystem("lamptop")
    """

    _ribout.write('CoordinateSystem "'+spacename+'"\n')

# RiTransformPoints
def RiTransformPoints(fromspace, tospace, points):
    """Transform a set of points from one space to another.

    This function is not implemented and always returns None.
    """

    return None

# RiCoordSysTransform
def RiCoordSysTransform(spacename):
    """Replace the current transformation matrix with spacename.

    Example: RiCoordSysTransform("lamptop")
    """

    _ribout.write('CoordSysTransform "'+spacename+'"\n')

# RiContext
def RiContext(handle):
    """Set the current active rendering context.

    Example: ctx1 = RiGetContext()
             ...
             RiContext(ctx1)
    """

    _switch_context(handle)

# RiGetContext
def RiGetContext():
    """Get a handle for the current active rendering context.

    Example: ctx1 = RiGetContext()
             ...
             RiContext(ctx1)"""

    global _current_context

    return _current_context

##################### Global variabels (internal) ####################

_contexts     = {}
_current_context = None

####

# Initially the output stream is stdout (and not an instance of RIBStream)
# In interactive sessions this prevents the version number to be written.
_ribout       = sys.stdout
_colorsamples = 3
_lighthandle  = 0
_objecthandle = 0
_errorhandler = RiErrorPrint
_declarations = {}

_insideframe  = 0
_insideworld  = 0
_insideobject = 0
_insidesolid  = 0
_insidemotion = 0

# If you're adding new global variables then make sure that they're
# saved and loaded from the context handling functions and initialized
# in RiBegin() and during the module initialization.

##################### Internal helper functions #######################

def _save_context(handle):
    "Save a context."
    ctx = (_ribout, _colorsamples, _lighthandle, _objecthandle,
           _errorhandler, _declarations,
           _insideframe, _insideworld, _insideobject, _insidesolid,
           _insidemotion)
    _contexts[handle]=ctx

def _load_context(handle):
    "Load a context."
    global _ribout, _colorsamples, _lighthandle, _objecthandle
    global _errorhandler, _declarations, _insideframe, _insideworld
    global _insideobject, _insidesolid, _insidemotion

    _ribout, _colorsamples, _lighthandle, _objecthandle, \
    _errorhandler, _declarations, \
    _insideframe, _insideworld, _insideobject, _insidesolid, \
    _insidemotion = _contexts[handle]

def _create_new_context():
    "Create a new context and make it the active one."
    global _current_context

    keys = _contexts.keys()
    if len(keys)>0:
        handle = max(keys)+1
    else:
        handle = 1
    _contexts[handle]=()
    
    if _current_context!=None:
        _save_context(_current_context)

    _current_context = handle

def _switch_context(handle):
    "Save the current context and make another context the active one."
    global _current_context
    
    if _current_context!=None:
        _save_context(_current_context)
    _current_context = handle
    _load_context(handle)

def _destroy_context():
    "Destroy the current active context"
    global _contexts, _current_context

    handle = _current_context
    del _contexts[handle]
    _current_context = None

def _init_declarations():
    global _declarations
    _declarations = {RI_P:"vertex point", RI_PZ:"vertex point",
                     RI_PW:"vertex hpoint",
                     RI_N:"varying normal", RI_NP:"uniform normal",
                     RI_CS:"varying color", RI_OS:"varying color",
                     RI_S:"varying float", RI_T:"varying float",
                     RI_ST:"varying float[2]",
                     RI_ORIGIN:"integer[2]",
                     RI_KA:"uniform float",
                     RI_KD:"uniform float",
                     RI_KS:"uniform float",
                     RI_ROUGHNESS:"uniform float",
                     RI_KR:"uniform float",
                     RI_TEXTURENAME:"string",
                     RI_SPECULARCOLOR:"uniform color",
                     RI_INTENSITY:"float",
                     RI_LIGHTCOLOR:"color",
                     RI_FROM:"point",
                     RI_TO:"point",
                     RI_CONEANGLE:"float",
                     RI_CONEDELTAANGLE:"float",
                     RI_BEAMDISTRIBUTION:"float",
                     RI_AMPLITUDE:"uniform float",
                     RI_MINDISTANCE:"float",
                     RI_MAXDISTANCE:"float",
                     RI_BACKGROUND:"color",
                     RI_DISTANCE:"float",
                     RI_FOV:"float",
                     RI_WIDTH:"varying float",
                     RI_CONSTANTWIDTH:"constant float",
                     "shader":"string",
                     "archive":"string",
                     "texture":"string",
                     "procedural":"string",
                     "endofframe":"integer",
                     "sphere":"float",
                     "coordinatesystem":"string",
                     "name":"string",
                     "sense":"string"
                    }

def _error(code, severity, message):
    global _errorhandler, RiLastError

    RiLastError = code

    st = inspect.stack(1)
    # Search the offending Ri function in the stack...
    j=None
    for i in range(len(st)):
        if st[i][3][:2]=="Ri":
            j=i
    # No function beginning with "Ri" found? That's weird. Maybe someone
    # messed with the function names.
    if j==None:
        where=""
    else:
        # name of the Ri function
        call   = inspect.stack(0)[j][3]
        # filename and line number where the offending Ri call occured
        file   = inspect.stack(1)[j+1][1]
        line   = inspect.stack(1)[j+1][2]
        if file==None: file="?"
        where = 'In file "'+file+'", line '+`line`+' - '+call+"():\n"

    _errorhandler(code,severity,where+message)

def _seq2col(seq):
    """Convert a sequence containing a color into a string."""
    if len(seq)<_colorsamples:
        _error(RIE_INVALIDSEQLEN, RIE_ERROR, "Invalid sequence length ("+\
               `len(seq)`+" instead of "+`_colorsamples`+")")
    colseq = tuple(seq)
    return '['+string.join( map(lambda x: str(x), colseq[:_colorsamples]) )+']'

def _flatten(seq):
    """Return a list of the individual items in a (possibly nested) sequence.

    Returns a list with all items as strings.
    If an item was already a string it's enclosed in apostrophes.
    
    Example: _flatten( [(1,2,3), (4,5,6)] ) -> ["1","2","3","4","5","6"]
             _flatten( ("str1","str2") )    -> ['"str1"','"str2"']
    """
    res = []
    ScalarTypes = [types.IntType, types.LongType, types.FloatType]
    StringType = types.StringType
    for v in seq:
        vtype = type(v)
        # v=scalar?
        if vtype in ScalarTypes:
            res.append(str(v))
        # vec3?
        elif isinstance(v, _vec3):
            res.extend([str(v.x), str(v.y), str(v.z)])
        # v=string?
        elif vtype==StringType:
            res.append('"%s"'%v)
        # no scalar or string. Then it might be a sequence...
        else:
            # Check if it is really a sequence...
            try:
                n = len(v)
            except:
                res.append(str(v))
                continue
            res += _flatten(v)
    return res

def _seq2list(seq, count=None):
    """Convert a sequence into a string.

    The function checks if the sequence contains count elements (unless
    count is None). If it doesn't an error is generated.
    The return value is a string containing the sequence. The string can
    be used as parameter value to RIB commands.
    """

    f = _flatten(seq)
    # Has the sequence an incorrect length? then generate an error
    if count!=None and len(f)!=count:
        _error(RIE_INVALIDSEQLEN, RIE_ERROR, "Invalid sequence length ("+\
               `len(f)`+" instead of "+`count`+")")
        
    return '[%s]'%" ".join(f)

def _paramlist2dict(paramlist, keyparams):
    """Combine the paramlists (tuple & dict) into one dict.
    
    paramlist is a tuple with function arguments (token/value pairs or
    a dictionary). keyparams is a dictionary with keyword arguments.
    The dictionary keyparams will be modified and returned.
    """

    if len(paramlist)==1 and type(paramlist[0])==types.DictType:
        keyparams = paramlist[0]
        paramlist = ()
    
    # Add the paramlist tuple to the keyword argument dict
    for i in range(len(paramlist)/2):
        token = paramlist[i*2]
        value = paramlist[i*2+1]
        keyparams[token]=value

    return keyparams

def _paramlist2lut(paramlist, keyparams):
    """Combine the paramlists into one dict without inline declarations.

    paramlist is a tuple with function arguments. keyparams is a
    dictionary with keyword arguments. The dictionary keyparams will
    be modified and returned.

    The resulting dictionary can be used to look up the value of tokens.
    """
    # Add the paramlist tuple to the keyword argument dict
    for i in range(len(paramlist)/2):
        token = paramlist[i*2]
        value = paramlist[i*2+1]
        # Extract the name of the token (without inline declaration
        # if there is one)
        f = token.split(" ")
        tokname = f[-1]
        keyparams[tokname]=value

    return keyparams
    
def _merge_paramlist(paramlist, keyparams):
    """Merge a paramlist tuple and keyparams dict into one single list.
    """
    if len(paramlist)==1 and type(paramlist[0])==types.DictType:
        keyparams = paramlist[0]
        paramlist = ()

    res = list(paramlist)
    # Check if the number of values is uneven (this is only allowed when
    # the last value is None (RI_NULL) in which case this last value is ignored)
    if (len(res)%2==1):
       if res[-1] is None:
           res = res[:-1]
       else:
           raise ValueError, "The parameter list must contain an even number of values" 

    # Append the params from the keyparams dict to the parameter list
    map(lambda param: res.extend(param), keyparams.iteritems())
    return res
    

def _paramlist2string(paramlist, keyparams={}):
    """Convert the paramlist into a string representation.

    paramlist is a tuple with function arguments (token/value pairs or
    a dictionary). keyparams is a dictionary with keyword arguments.
    Each token has to be a string, the value can be of any type. If the
    value is a string, it's enclosed in apostrophes. A trailing token
    without a value is ignored, which also means that a trailing RI_NULL
    can be passed.
    The resulting string contains a leading space, unless there are no
    token/value pairs.
    """

    global _declarations

    paramlist = _merge_paramlist(paramlist, keyparams)

    res=""
    for i in range(0, len(paramlist), 2):
        token = paramlist[i].strip()
        value = paramlist[i+1]
        # Extract the name of the token (without inline declaration
        # if there is one)
        f = token.split(" ")
        tokname = f[-1:][0]
        inline  = f[:-1]

        if not (_declarations.has_key(tokname) or inline!=[]):
            _error(RIE_UNDECLARED,RIE_ERROR,'Parameter "'+tokname+
                   '" is not declared.')
        
        # Check if the value is a sequence (if it returns an iterator)
        isseq=0
        try:
            isseq = (iter(value)!=None)
        except:
            pass
        # Convert value into the appropriate string representation
        if type(value)==types.StringType:
            value='["'+value+'"]'
#        elif type(value)==types.ListType or type(value)==types.TupleType:
        elif isseq:
            value = _seq2list(value)
        else:
            value='[%s]'%value
        res+=' "'+token+'" '+value

    if (res==" "): res=""
    return res


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

_init_declarations()

if __name__=='__main__':

    RiBegin(RI_NULL)
    RiErrorHandler(RiErrorAbort)

    RiWorldBegin();

    RiWorldEnd()

    RiSkew(45,0,1,0,1,0,0)
    RiSkew(45,[0,1,0],[1,0,0])
    
    RiEnd()
    
