Scripting for Helga

This page is intended for developers that want to write Helga-compliant production scripts and use them with Helga, use their old non-Helga scripts with Helga, or wire a scriptable program such as Maya or Shake to their Helga site. This document will clearly lay out the guidelines for accomplishing these goals.

You should read The Helga File Structure before proceeding with this document. This document requires an understanding of the Helga file structure.

Also see Tool Versioning for what happens when you want to add a new version of your script to Helga.

Requirements

Here is an excerpt from the The Helga Specification outlining the requirements of Helga scripts:

There are a few things that every Helga compliant script should be able to do:

  • Return usage information - the arguments that need to be supplied to the script for it to run and how those values affect the outcome of the script.
  • Return interface building information - how each argument could be supplied in a visual interface - should it be a textbox, a pull down menu, a checkbox?
  • Do a "test run" - build all of its argument data and display information about what would have happened if the script had executed. Scripts can pull their input data from many different places, so it is important for users to be able to see what data the script is planning on using without running the script itself.

By writing a Helga-compliant script, you need not write any of the boiler plate code to accomplish these goals; Helga will use the information you give to perform these tasks.

Writing the Script

Your Helga script is a file on disk, written in Tcl.

There are two things you must define in your script: an argument list and the body proc. After writing your script, you may then add it as a script asset to your Helga site.

We'll follow the whole process, through script creation, the adding of the script to Helga, and the running of the script in Helga.

For all of the steps in the following example, it is assumed that your Helga script will exist on disk at /helga/site/global/trunk/scripts/render.tcl

Step #1: Script Layout

As stated above, your Helga script must contain two things: an argument list and a body proc. They will be arranged in the file like this:

proc argList {} {

}
proc bodyProc args {

}

For those developers that have written Helga commands before, this layout will look very familiar. In fact, it uses the exact same argument parsing mechanism.

This setup creates the argument list and body proc as Tcl procs. The argument list and proc code itself are defined within the respective curly brackets of each. Any additional code outside these brackets will not be used. It doesn't matter what the body proc is named; the argument list name should fit argument list naming conventions ("<body proc name>Args") but this is also not necessary.

Now let's take a look at what needs to go in each of those pairs of curly brackets.

Step #2: The Argument List

As mentioned, the argument list format is the same as it is for command argument lists, though we'll repeat that information here for those who have no intention of writing HelgaShell code.

Argument lists are actually Tcl procs, which return a Tcl list. An argument list proc is formatted like this:

proc argListName {} {
      return {
            { <argument name> <argument text> <default value> <description text> [<web name> [<web input type> [<possible values>]]] }
            ... repeat for each argument ...
      }
}

To break down each element in this list:

<argument name>

The general identifier of the argument, and the name of the variable into which this value will be stored. To create a dashed, optional argument, the first character should be a "-". This leading character will be stripped to create the variable.

<argument text>

A short line of text describing in more detail the value that the argument will hold. This information is not used in HelgaWeb?. If this element is left blank (""), and <default value> is set to "true" or "false", a boolean argument will be created (no value needs to be passed with the argument).

<default value>

The default value of the argument, if not specified by the user. The variable named by <argument name> will initially be set to this. In HelgaWeb?, the form element specified will be given this initial value. If left blank ("") and <argument name> is not a dashed argument, the argument created will be required to be specified by the user.

<description text>

A more descriptive block of text describing the argument in more detail. Appears in the output of the husage command and can be accessed from HelgaWeb?.

<web name>

The name of the argument in a more human readable format. As <argument name> is restricted to the same rules as the names of all Tcl variables, it is ugly to have these names appear on a web UI. Use this argument to specify a string to show instead. E.g. if <argument name> is "assetName", <web name> might be "Asset Name".

<web input type>

The type of web form element to use. May be a blank string to omit it from the web form. Must be one of "text", "password", "textarea", "checkbox", "radio", or "select". We'll define the behavior of some of these types:

  • text/password/textarea - creates a text entry element. Initial value will be <default value>.
  • checkbox - creates a checkbox. <default value> may set the initial state of the checkbox - 1, true, or "checked" will result in it being checked; 0, false, "unchecked", or any other value will result in it being unchecked.
  • select - creates a dropdown menu. use <possible values> to hold a list of values for the dropdown. , and it will be selected by default. If <default value> is "", the first element of <possible values> will be selected by default.
  • radio - creates a set of radio buttons. Use <possible values> to hold a list of the radio buttons that will be created to select from. If <default value> is "", the first element of <possible values> will be selected by default.

<possible values>

Holds a comma-separated list of valid values for the argument. If set, the value passed by the user must match at least one element of this list. Also used to build certain form elements for the web interface. If <default value> and <possible values> are both set to anything other than "", <default value> must match one of the elements in <possible values>.

Back to our example. Based on what we said above, we'll modify our Helga script to look like this:

proc renderArgs {} {
      return {
            { assetName "<scene file asset>" "" "The asset name of the maya ascii file you want to render." "Asset Name" text }
            { -frange "<frame list>" "" "The frame range you want to render. You may designate ranges with a dash and chunks of either a single frame 
or a range by separating the chunks with a comma, e.g. 1-3,5,7-10." "Frame List" text "" }
            { -xres "<x resolution>" "" "The x resolution to render at." "X Resolution" text "" }
            { -yres "<yresolution>" "" "The y resolution to render at." "Y Resolution" text "" }
            { -version "<version number>" "" "The version number of the script to use. If not given, will find a version based on 
<project>Ver or globalVer attributes." "Script Version" select [::helga::command::getExportedVersions /scripts/render] }
      }
}
proc render args {
}

First, let's see how this argument list would influence an husage command. This is for demonstration purposes! In our example the script has not been added to Helga yet, so this command would not really work.

% husage hexec /scripts/render
Usage: hexec /scripts/render <scene file asset> [-frange <frame list>][-xres <x resolution>][-yres <yresolution>]
[-output <output directory>]
Where:
        <scene file asset> = The asset name of the maya ascii file you want to render.
        <frame list> = The frame range you want to render. You may designate ranges with a dash and chunks of either a single frame or a range
by separating the chunks with a comma, e.g. 1-3,5,7-10.
        <x resolution> = The x resolution to render at.
        <y resolution> = The y resolution to render at.
        <version number> = The version number of the script to use. If not given, will find a version based on <project>Ver or globalVer attributes.

Step #3: The Body Proc

A body proc may:

  • Execute commands directly with the Tcl command "exec"
  • Return a Tcl list of commands to be executed

This establishes two stages of script execution:

  1. Preflight: within the your script body. You have access to user input and return values.
  2. Main: within the Helga core, after the script body has executed. Helga runs each command returned by the script body.

For our example we'll assume most of our rendering functionality is encapsulated in a separate external render script. This could be a legacy render script your studio has already written, or it could be a command line call directly to a rendering application like Maya.

We would normally call our legacy render script from the command line like this:

/helga/site/global/trunk/scripts/legacyRender.tclsh /helga/project1/shot1.ma -frange 1,3,5-10 -output /helga/project1/renders/shot1 -xres 1920 -yres 1080

We don't want to type all of this out every time we render. We want Helga to automatically generate these values by using asset attributes and project-wide defaults, while allowing users to override any of these options. Since this is a site-wide script we also don't want to hardcode the project-specific settings directly, and it's definitely not something that we should code into a script that will be shared with other sites.

Knowing all of this, the next version of our script will look this:

proc renderArgs {} {
      return {
            { assetName "<scene file asset>" "" "The asset name of the maya ascii file you want to render." "Asset Name" text }
            { -frange "<frame list>" "" "The frame range you want to render. You may designate ranges with a dash and chunks of either a single frame 
or a range by separating the chunks with a comma, e.g. 1-3,5,7-10." "Frame List" text "" }
            { -xres "<x resolution>" "" "The x resolution to render at." "X Resolution" text "" }
            { -yres "<yresolution>" "" "The y resolution to render at." "Y Resolution" text "" }
            { -version "<version number>" "" "The version number of the script to use. If not given, will find a version based on
<project>Ver or globalVer attributes." "Script Version" select [::helga::command::getExportedVersions /scripts/render] }
      }
}
proc render args {
        #
        #    Parse the input arguments as laid out in the Helga Code Notes. Our input
        #    arguments are in the "args" variable, the command name we're using is "hexec
        #    /scripts/render", as that's where our script will be stored in the asset
        #    structure, and the command argument list we're using will be retrieved by
        #    running the "renderArgs" proc, which we've defined above.
        #    
        ::helga::command::parseArgs $args "hexec /scripts/render" [renderArgs]
        #
        #    Use getRevisionPath to get the path of the script on disk.
        #    If $version was given, getRevisionPath will make sure that version exists and is exported,
        #    and will export it if not. if $version was not given, getRevisionPath will determine the 
        #    default version to use.
        #
        append outString "[::helga::command::getRevisionPath /scripts/render $version]"
        #
        #    Grab the path on disk of the file we're about to render. We know that
        #    the asset name of the script passed in will be stored in the variable "assetName". We also
        #    know that that asset's path on disk will be stored in its attribute "filePath".
        #    We'll add this value to our string to be executed.
        #
        append outString "[hget attr filePath -name $assetName]"
        #
        #    Check to see if the user has supplied the -frange argument. If not, we won't bother
        #    to specify it ourselves, it'll just default to whatever is in the scene file. Notice that
        #    we can check to see if the argument was given by seeing if it is equal to its default
        #    value of "".
        #
        if { $frange != "" } {
                append outString "-frange $frange "
        }
        #
        #    Build the image output path, also from the project name. For this we assume some
        #    things about our site setup. We assume that our asset hierarchy follows the
        #    convention of /<project name>/<sequence name>/<shot name>, and we want our
        #    rendered images folder to have a similar structure, with the convention
        #    /helga/site/<project name>/renders/<sequence name>/<shot name>.
        #
        #    We'll start with the base of that path.
        #
        set outputPath "/helga/site/$projectAsset/renders/"
        #
        #    Split our asset path into a flat list of basenames.
        #
        set assetSplit [::helga::assets::splitPath $assetName]
        #
        #    Our sequence and shot names are at list index 1 and 2 in our split path.
        #
        append outputPath "[lindex $assetSplit 1]/[lindex $assetSplit 2]"
        #
        #    Now append that whole path to the command string with the argument name.
        #
        append outString "-output $outputPath "
        #
        #    Check for a user-supplied xres and yres. If not given,
        #    get the value of the file's xres and yres attributes with the -inherit option set.
        #    If they haven't set their own values for those attributes, we'll defer up the     
        #    hierarchy. We know that for our site we can rely on each project to have
        #    xres and yres set. If something goes wrong and we still don't find it, we know
        #    our render script will default to the scene file's settings.
        #
        if { $xres != "" } {
                append outString "-xres $xres"
        } else {
                set $xAttr [hget attr xres $assetName -inherit]
                if { $xAttr != "" } {
                         append outString "-xres $xAttr"
                }
        }
        if { $yres != "" } {
                append outString "-yres $yres"
        } else {
                set $yAttr [hget attr yres $assetName -inherit]
                if { $yAttr != "" } {
                         append outString "-yres $yAttr"
                }
        }
        #
        #    We're all done! Return the full command string, wrapped in a Tcl list.
        #
        return [list $outString]
}

Our resulting script is mostly comment text; with very little code we're are able to translate our Helga input data into a non-Helga command to be executed.

Now that our wrapper script is complete, we can add our script as an asset to Helga.

Step #4: Adding Your Script to Helga

We'll start by getting the usage information of hadd script:

/> husage hadd script
Usage: hadd script <path to file> [-name <asset name>] [-list <list name>] [-fullname <full name>] [-desc <description text>] [-tags <tag list>] [-frameRange <numerical frame range>] [-setRange <numerical range>] [-version <version number>] [-allowedTypes <type list>] [-machine <machine name>] [-machineTags <machine tag list>]
Where:
	<path to file> = The file's path on disk.
	<asset name> = The desired name of your new asset, using either a full asset path or current asset shorthand. If neither -name nor -list are given, the new asset will be added as a child to your current asset and assigned a new site-unique name of the form a#.
	<list name> = The name the desired containing list of the new asset. Overriden by -name. Use this if you know what list asset you want, but you want to automatically assign a base name in the form a#.
	<full name> = A more descriptive text name for the new asset. May contain letters, numbers, and whitespace.
	<description text> = A full-text description of the asset. May include any characters.
	<tag list> = one or more short text labels to attach to the asset. Labels may contain letters, numbers, underscores, and whitespace. Each tag must be enclosed in quotes. If providing more than one, they must be in a comma-seperated list.
	<numerical frame range> = The frame range within the file, e.g. for files made in time-based 3d appications.
	<numerical range> = If given, allows you to specify a set of files on disk with similar names to be represented by a single asset in Helga. It must be a contiguous numerical range. If given, replace the piece of <path to file> that is an instance of this range with the string "{x}". e.g., if you want to specify a set of files named frame1.tif-frame50.tif, <path to file> should be frame{x}.tif and -setRange 1-50 should be given. Number padding is implied, i.e. if you want to specify number padding of 4, specify -setRange 0001-0050.
	<version number> = The version number of the script. If not given, defaults to 1. If a script already exists with the same full name and version, version will be incremented by 1 until a unique full name/version pair is found.
	<type list> = An asset type, or a comma-separated list of asset types, that the script is allowed to operate on. Defaults to "file".
	<machine name> = The name, or a comma-separated list of names, of the machine that this script is allowed to run on. Defaults to "", if left blank the script will be run directly on your Helga server.
	<machine tag list> = A tag or comma-separated list of tags. If given, the script will only run on machines that are associated with all of the given tags. If given along with -machine, they will operate together in an "or" fashion, as in "use machines that have this name or these tags".

Now we have a list of the things we need to collect in order to add this script.

<file path>

We already know our script is on disk at /helga/site/global/trunk/scripts/render.tcl. This path will be parsed by hadd to make sure the path fits into Helga's versioning support system, which means that since the script lives under "global", the command "hget attr scriptRoot -name /" will need to be the base of its path. The assumed scriptRoot here is /helga/site/global.

This path also implies a version: "trunk". If the "trunk" branch exists in your scriptRoot, the check will pass. If not, Helga will try to do an svn export to create the new branch for you.

See Tool Versioning for more information on those conventions.

<asset name>

We've also already decided on an asset name, /scripts/render. This fits the Helga convention for where in the asset structure to place global scripts.

<full name>

"Global Render Script"

<summary text>

"Renders a Maya ascii file."

<version number>

This has already been implied by our file path, so we can leave it out. If we were adding a new version of this script instead, we could specify a which version it is here. The path given in <file path>, in that case, would then be used only if it fit the convention of scriptRoot/version. Otherwise, the script found at <file path> would be copied to the correct location.

<type list>

We want our script to run only on .ma files, the Maya ascii file. We can assume that Maya scene files will be some derivative of the file asset type, so let's find out what the exact name of that type is by getting a list of all types deriving from "file":

/> htypes file
image mayaAscii script shake video

We see that mayaAscii exists as an asset type derived from file.

<tag list>, <machine list>, <machine tag list>

We're not going to worry about these for now.

Now we have all the information we need. Following the usage info, we'll use the following command:

/> hadd script /helga/site/global/trunk/scripts/render.tcl -name /scripts/render -fullname "Global Render Script" \
-desc "Renders a Maya ascii file." -allowedTypes mayaAscii
/scripts/render

And our script is added! We'll check it out with another command:

/> array set result [hget attrs /scripts/render]
/> parray result
result(assetId) = 57
result(assetName) = render
result(typeId) = 7
result(filePath) = /helga/site/global/trunk/scripts/render.tcl
result(fullName) = Maya Render
result(summary) = "Renders a Maya ascii scene file."
result(version) = 1
result(targetTypeId) = 12

Now our legacy render script has been given a wrapper to adapt it to Helga, and made available as an asset, to be integrated with the rest of your Helga site. Now let's take a look at how to use it.

Step #5: Running Your Script

Let's take a second just to make sure that we know where we are, and that our script is where we think it is:

/> hcd /scripts
/scripts
/scripts> hls
/scripts/render

The Helga command "hexec" provides us with a standard method of running our Helga scripts. Let's check out the usage of hexec:

/scripts> husage hexec Usage: hexec <script name> Where:

<script name> = the full or relative path to the script you want to run. For additional usage information, use the command "husage hexec <script name>".

We'll provide husage with the name of the script to get usage info specific to the script. This probably looks familiar, as we've already shown it in Step #2.

% husage hexec /scripts/render
Usage: hexec /scripts/render <scene file asset> [-frange <frame list>][-xres <x resolution>][-yres <yresolution>]
[-output <output directory>]
Where:
        <scene file asset> = The asset name of the maya ascii file you want to render.
        <frame list> = The frame range you want to render. You may designate ranges with a dash and chunks of either a single frame or a range
by separating the chunks with a comma, e.g. 1-3,5,7-10.
        <x resolution> = The x resolution to render at.
        <y resolution> = The y resolution to render at.
        <version number> = The version number of the script to use. If not given, will find a version based on <project>Ver or globalVer attributes.

We know that some of this information will be made available automatically by the script's preflight, but we're not sure which. We could check out the preflight script, but let's make use of hexec's -test mechanism:

/> hcd scripts
/scripts> hexec render /project1/seq1/shot1/sceneFile -test
Command: /helga/scripts/render.tcl /helga/site/project1/seq1/shot1/shot1.ma -xres 640 -yres 360
-output /helga/site/project1/renders/seq1/shot1

Looks good. Let's run it.

/scripts> hexec render /project1/seq1/shot1/sceneFile
Command: /helga/scripts/render.tcl /helga/site/project1/seq1/shot1/shot1.ma -xres 640 -yres 360
-output /helga/site/project1/renders/seq1/shot1
Starting render...render launched in background.

Now, any shot in any of your projects could be rendered with a single, simple command, with helpful command-line usage and an auto-built web form for use within HelgaWeb.