Intro to Slim Scripting

Introduction to Slim Scripting

Background
The Slim Object
The TreeNode Class
Palette Objects
Appearance Objects
Property Objects
The Workspace Object
Output
User Data
Commands

Background

You can write scripts to perform all the actions in Slim that you might perform interactively. RenderMan Studio 3 re-introduced support for the slimcmd and slimmsg MEL commands, or you can write MEL scripts to control Slim by wrapping the Slim commands in rman slim message "<tcl stuff>". In this way you can treat Slim as an extension of Maya and develop MEL scripts that couple manipulations of geometry and Slim objects. This document serves as an introduction to scripting with Slim.

Using the Slim console you can interactively develop, debug and invoke the scripts you write. You can install developed Tcl scripts (see http://tcl.sourceforge.net/) by adding a LoadExtension directive to one of several initialization files.

Slim supports an object oriented programming paradigm. Slim commands are generally targeted to a unique object. Objects are of a particular class and all objects of the same class accept the same commands, or methods. Object handles are the means by which we identify a particular object. These handles are actually transitory Slim commands. Because they are transitory, they should not be hard coded into your scripts. Each time a script is run, it must locate object handles via appropriate commands.

Users should also consult the RfM Tcl Scripting documentation for additional information.

The Slim Object

There is one object that you can hard code into your scripts. This object handle is slim and it represents Slim's application context. The slim object is the starting point for many of the scripts you will write. You can query the slim object for the active palettes or appearances. These queries will return object handles which you can use to perform further queries.

The following example code illustrates the typical workflow in a Slim script. This snippet will find all parameters named "Ks" and set them to 0. It executes by querying Slim for all active appearances, querying those appearances for parameters which match our criteria, and executing the SetValue method on the parameters that are found:

    foreach app [slim GetAppearances] {
	foreach p [$app GetProperties -name "Ks" -access input] {
	    $p SetValue 0
	}
    }
    
The GetAppearances method is probably the most useful method of the slim object. Here are some of the other most useful methods of the slim object:
slim BuildShaders ?arg arg ...?

Generate and build all shaders associated with attachable functions.

slim CreatePalette ?arg arg ...?

Create or open a Slim palette.

slim GetPalettes ?arg arg ...?

Get object handles for all top-level palettes.

slim GetTemplates ?arg arg ...?

Get a list of all active templates.

The complete list of methods available for the slim object, along with their usage and arguments can be found in the Slim Scripting Glossary.

The TreeNode Class

In the above example, we are operating on three classes of objects: the slim object, appearance objects, and property objects. The slim object is unique. It is the only object of its class and no other classes inherit from it. The same is true of the workspace class (discussed below).

The rest of the classes you will work with, however, will have some things in common. All of the objects in these classes can be arranged in a tree-like structure. Palettes can be grouped within palettes (these are referred to as subpalettes). Appearances can be collected within other appearances. Properties can be grouped into a collection. Because these classes all have some common functionality, they all inherit from a common base class called TreeNode.

The TreeNode base class defines basic methods for organizing nodes. The names of these methods follow the metaphor of a family tree. Nodes are grouped together under a parent. Those nodes are their parents children. In the following procedure, we search for an appearance with a name specified by the name argument, and then group all similarly named appearances underneath it. TreeNode methods will be highlighted in bold.

    proc findAndGroupSimilar {name} {
	# find the appearance matching "name"
	set parentApp [slim GetAppearances -name $name]

	# get the root of the appearance's tree (a palette)
	set plt [$parentApp GetRoot]

	# get the list of Chrome's current children
	set children [$parentApp GetChildren]

	# find all appearances that start with $name
	foreach app [$plt GetAppearances -name "${name}*"] {
            # skip the original
            if {$app == $parentApp} {
                continue
            }

	    # check if this appearance is already a child of Chrome
	    if {-1 == [lsearch $children $app]} {
		# app isn't in the list, make it a child of Chrome
		$parentApp AddChild $app
	    }
	}
    }
    

In the code above, we used TreeNode methods GetRoot, GetChildren, and AddChild. These methods are defined for any class that inherits from TreeNode. The classes we discuss below all inherit from TreeNode. A complete list of methods defined for the TreeNode class can be found in the Slim Scripting Glossary. To visualize the whole tree of classes that inherit from TreeNode, refer to the Class Hierarchy Diagram.

Palette Objects

Palettes are containers of appearances and other palettes. In Slim your can have any number of Palettes and these can be organized hierarchically. You can search palette objects for appearance objects that match various criteria. You can also encapsulate the state of a palette into an ASCII string.

The following procedure finds all of the palettes active in a scene, adds a new node specified by tmplt argument, and then saves any palette files that are external:

    # call this template with a valid template ID, e.g. "pixar,RIBBox#0"
    proc addTemplateToAllPalettes {tmplt} {
	# loop through all top-level (non sub-) palettes
	foreach pal [slim GetPalettes] {

	    # check if this template exists in the palette already
	    # if it does, skip
	    if {[$pal GetAppearances -template $tmplt] != ""} {
		continue
	    }

	    # create an appearance using the template
	    $pal CreateAppearance -template $tmplt

	    # if this is an external palette, save it
	    if {[$pal GetStorageType] == "external" && \
		[$pal GetAccessMode] == "rw"} {
		$pal Save [$pal GetFilename]
	    }

	    # force a redraw of the editor
	    $pal UpdateEditor
	}
    }
    
In the example above, we used the methods GetAppearances, CreateAppearance, GetStorageType, GetAccessMode, Save, GetFilename, and UpdateEditor. Here are some other useful methods defined for Palette objects:
plth DeleteDisconnected

Delete any nodes in the graph that are not attachable and are not connected to any other attachable nodes.

plth Edit

Raise the Palette Editor for this object.

plth SelectAppearances applist

Select the appearances specified by applist.

A complete list of methods defined for palettes can be found in the Slim Scripting Glossary.


Appearance Objects

An Appearance is a container of properties and is the base class for the various appearance types in Slim. Functions are a type of appearance that can participate in custom shader generation. Every function object has an associated Template object which itself is a type of appearance. Instances are a type of appearance that can't participate in custom shader generation. All of these classes of objects share a lot of behavior including the ability to search for properties that match various criteria.

As an example, the following procedure finds the appearance with name matching the passed argument. It then creates a duplicate copy of the appearance, opens the appearance in the Appearance Editor, checks its shading rate, and renders a preview.

    proc findCopyAndEdit {name} {
        # get appearances that match string
        set apps [slim GetAppearances -name $name]

        # loop through the appearances
        foreach app $apps {
            # create copy of appearance
            # the result may be a list if $app has connected nodes
            set newApps [$app Duplicate]
            set newRoot [lindex $newApps end]

            # make sure shading rate is set to 1 (or lower)
            set curRate [$newApp GetPreviewShadingRate]
            if {$curRate > 1} {
                $newApp SetPreviewShadingRate 1
            }

            # raise in Appearance Editor
            $newApp Edit

            # render preview
            $newApp PreviewRender
        }
    }
    

The procedure above made use of the Duplicate, GetPreviewShadingRate, SetPreviewShadingRate, Edit, and PreviewRender methods. These are some other useful methods for appearances:

apph GetProperties ?arg arg ...?

Get the list of an appearance's properties that match the search criteria. This will be covered further in the Property Objects section (below).

apph RevertValues

Revert all properties in the appearance to their respective default values.

apph UpdateEditor

Update the Appearance Editor for the appearance.

A complete list of Appearance methods, along with methods specific to Function objects, can be found in the Slim Scripting Glossary.


Property Objects

Properties are where most of the information associated with appearances are stored. As with appearances, the property class is a base class for other subclasses. Collections, Parameters, Attributes and TORAttributes are the subclasses of Property. Collections are used to organize properties hierarchically. Parameters are those properties that are actual appearance parameters. Attributes are those properties that have a RIB representation that isn't part of a shader instance. SlimAttributes can be used to control code generation. TCLAttributes contain tcl code that may be executed within a TCLBox. TORAttributes are properties that control how additional rendering passes are performed.

Properties exist inside of appearances, and are accessed using the GetProperties appearance method. This method can be used with a number of different switches to filter the properties returned. Given a variable apph set to the object handle of an appearance, you might use GetProperties in the following ways:

    # get all input properties with name "Ks"
    $apph GetProperties -access input -name Ks

    # get all float input parameters
    $apph GetProperties -type float -access input -class ::Slim::Parameter

    # get all external parameters
    $apph GetProperties -provider variable -access input -class ::Slim::Parameter
    

Because this method is defined for appearances, you will only be operating on the properties of one appearance at a time. If you want to operate on the properties across a palette, or an entire session, you must do so by nesting loops. The example given in our explanation of The Slim Object does just this.

The following procedure copies property settings from one node to another. Property correspondence is done by name, so the nodes do not have to be of the same template.

    proc copySettings {fromName toName} {
        # get source appearance
        set fromApp [slim GetAppearances -name $fromName]

        # if this does not return exactly one appearance,
        # print an error and exit
        set numFrom [llength $fromApp]
        if {[llength $fromApp] != 1} {
            ::RAT::LogMsg ERROR "copySettings : fromName must matches $numFrom appearances"
            return
        }

        # loop through destination appearance(s)
        foreach toApp [slim GetAppearances -name $toName] {

            # loop through properties in fromApp
            foreach fromProp [$fromApp GetProperties -access input] {

                # find properties in toApp with the same name
                set name [$fromProp GetName]
                foreach toProp [$toApp GetProperties -access input -name $name] {
                    if {![$toProp isa ::Slim::AlterEgo::coll]} {
			# copy value
			$toProp SetValue [$fromProp GetValue]
                    }

                    # copy value provider
                    set provider [$fromProp GetValueProvider]
                    $toProp SetValueProvider $provider

                    # if it's a connection, set that
                    if {$provider == "connection"} {
                        $toProp SetConnection [$fromProp GetConnection]
                    }
                }

                # log copy
                ::RAT::LogMsg INFO "Copied settings from [$fromApp GetName] to [$toApp GetName]"
            }
        }
    }
    
The example above made use of the GetName, GetValue, SetValue, GetValueProvider, and SetValueProvider methods for properties. These are some other useful methods for properties:
proph GetAppearance

Get the object handle for the appearance that uses this property.

proph GetConnectedFunction

Get the object handle for the function to which this property connects.

proph RevertValue

Revert the value for this property to its default.

A complete list of methods defined for Properties, especially those specific to AtomicProperties (those with a value), can be found in the Slim Scripting Glossary.

You might have noticed the isa query in the example above. This is a standard method available to all classes, and you can use it to check the class of an object. If you ever see the name of an object handle (e.g. coll153), you will find that the prefix of the handle is its class. A fully qualified class for a scripting object will be ::Slim::AlterEgo::class, e.g. ::Slim::AlterEgo::coll. You can query the class of any object using the info method, e.g.:

    % coll161 info class
    ::Slim::AlterEgo::coll
    % coll161 isa ::Slim::AlterEgo::coll
    1
    

An complete index of classes can be found in the Slim Scripting Glossary.

The Workspace Object

This class is responsible for managing issues related to the current workspace. As with the application object, there should only be a single instance of this class and its handle is workspace. You can use the workspace object to convert path names from global to relative references using the current search paths. The workspace state is manipulated by the Workspace Editor and is managed across applications. For this reason it may not be sufficient to simply set the workspace state using the workspace object.

Methods for the workspace can be found in the Slim Scripting Glossary. For more details on working with workspaces, please refer to the Asset Management documentation.


Output

You may find it useful to output information while your script is running, either to communicate to the user, or to help with debugging information.

Because Slim is designed to run in conjunction with a client application like Maya, the stdout channel is not usable for output. Instead, you can direct information to Slim's message log.

The advantage of using Slim's message log is that each message is automatically timestamped and can be coded according to' severity. You can log it using the ::RAT::LogMsg procedure. Just send your message, preceded by its severity:

    ::RAT::LogMsg COPIOUS "Reading from testdata.tcl"
    ::RAT::LogMsg INFO    "Creating a new appearance"
    ::RAT::LogMsg NOTICE  "This appearance has been changed"
    ::RAT::LogMsg WARNING "This appearance contains a very low shading rate"
    ::RAT::LogMsg ERROR   "Cannot generate shader"
    ::RAT::LogMsg SEVERE  "Cannot communicate with Maya"
    

User Data

As you develop scripts, you may find it useful to associate extra information with an appearance or with a palette. The User Data mechanism allows you to associate arbitrary information with any node in Slim.

Every node in Slim has a user data dictionary, or data that is stored and retrieved using a key. Any string can be used as a key. Slim makes no assumptions about its contents. User Data is only accessible through the scripting environment. It will not be visible anywhere in the interface.

As an example of how to use User Data, this procedure tags every active appearance with the current time, host, and user.

    proc storeCreationInformation {} {

        # loop through all appearances
        foreach app [slim GetAppearances] {

            # check for existing "createdBy" key
            set user [$app GetUserData createdBy]

            # if this has already been set, skip
            if {$user != ""} {
                continue
            }

            # store user name, time, and host
            $app SetUserData createdBy [GetUserName]
            $app SetUserData createdAt [clock seconds]
            $app SetUserData createdOn [GetHostName]
        }
    }
    

We can later retrieve that information using the procedure below:

    proc retrieveCreationInformation {} {
        # loop through all appearances
        foreach app [slim GetAppearances] {

            # check for existing "createdBy" key
            set user [$app GetUserData createdBy]

            # if this hasn't been set, skip
            if {$user == ""} {
                ::RAT::LogMsg NOTICE \
                    "Could not find creation information for [$app GetName]"
                continue
            }

            ::RAT::LogMsg INFO "Creation information for [$app GetName]"
            ::RAT::LogMsg INFO " - created by $user"
            
            # get time and format it
            set time [$app GetUserData createdAt]
            if {$time != ""} {
                ::RAT::LogMsg INFO " - created at [clock format $time]"
            }

            # get host
            set host [$app GetUserData createdOn]
            if {$host != ""} {
                ::RAT::LogMsg INFO " - created on $host"
            }
        }
    }
    

Commands

Commands allow you to present your scripts as items in the Commands menus of the Appearance Editor and the Palette Editor. The following trivial example demonstrates how to create a Command:

Creating the .slim File

Commands, like templates, are defined in .slim files. A declaration of a custom command consists of an invocation — how the command should appear and the tcl procedure to call when it is invoked — and a body of TclTkSource — a container in which to define tcl procedures. This example custom command will be displayed in the Appearance Editor and, when invoked, will log all of the values of the current appearance to Slim's message log.

##
## Creating a Custom Command
##

# Tell Slim this is an extension
slim 1 extensions pixardb {
    extensions pixar pxsl {

	# Begin custom command
	cmdui printValues {

	    # Place the command in the Command Menu of the Appearance Editor.
            # The first argument defines how the item will appear.
            # The second argument defines the procedure to call.
            # Note %c will automatically be substituted for the
            # handle of the current apperance or palette.

            invocation {AppearanceEditor/Resources/Print Param Values} {::Slim::printValues %c}

	    TclTkSource {

		# Here's the tcl command
		# Because we used "%c" in our invocation above,
		# The current context (in this case, the current appearance)
		# will be passed as the first argument

                proc ::Slim::printValues app {

		    ::RAT::LogMsg INFO "Printing parameter values for [$app GetName]"

                    # loop through parameters, printing their values
                    foreach p [$app GetProperties -access input -class ::Slim::Parameter] {
                        ::RAT::LogMsg INFO " - [$p GetLabel] : [$p GetValue]"
                    }
		}
	    }
        }
    }
}

Loading the .slim File
Like templates, custom commands must be loaded in an initialization file. You use the LoadExtension procedure, followed by the customcmd class, as below:

LoadExtension slim [file join $templates custom_commands.slim] customcmd

Running the Command
After restarting Slim, our command should be positioned in the Commands menu as specified by its Invocation:

Executing the "Print Param Values" command yields these results in the message log:

Customizing the Invocation
Optional arguments to the invocation command allow you to further customize how your Command appears in the menu:

-accelerator binding
Establishes a hot-key to invoke your command. The binding should be declared like a Tk binding, e.g. <Control-m>.
-enabled expression
Controls whether the menu item is enabled. The argument is a TCL expression that will be evaluated when the menu is posted. As an example, you can disable commands that may depend on Maya/MTOR using:
       
           -enabled {[string match [slim GetRunMode] "server"]}
You can use %c in your expression, and it will be replaced by the context (that is, the appearance or palette) of the editor. Remember to enclose your expression in braces so it is evaluated when the menu is posted, and not before.