CS3Tools.cui Tutorial

The CS3Tools Custom User Interface implements several very useful tools. This tutorial will concentrate on the Custom Keys Utility, a custom keys mechanism that gives one-touch access to 6 key frame settings that you use most when animating a Biped. The Custom Keys Utility uses 13 icon buttons. The first 6 are "Set custom Biped Keys". The next 6 are "Change Preset to current key". The following one is "Launch Preset Floater".

Once you have stored your favorite custom keys, animation workflow is greatly improved, since you spend little or no time tuning default key frame attributes that don't fit your current character's situation or style. Simply pose your character and click the custom key icon that fits your current situation.

Overview

Custom Keys are implemented via a new dedicated 3ds max Tool Bar, called Character Studio Tool. This tool bar is the home base for a growing number of MAXScript-enabled character studio commands. All operations associated with Tool Bars are available. For example, individual icons can be relocated, deleted, and renamed. Tool tips can also be customized, as implied above, to let you annotate each custom key type for rapid recall. Custom Keys are displayed on the Character Studio Tool Bar as color-coded "set key" icons, similar to the standard Biped red set key icon in the Biped Motion panel. Each color indicates a specific custom key type. There are two icons for each key type û one for setting a key, and second for storing the preset value, based on the currently selected track and key. The second icon û used for storing the preset values û displays a small black arrow pointing to the center of the icon û indicating that a value will be stored. Once you have set your keys as you like them, you may choose to delete the storage icons entirely, to save space. You can also float or re-dock the Custom Key icons to customize access to suit your style and needs. Additionally, you can launch a floating rollout of the same set of custom keys.

To use Custom Keys, simply set a Biped key as you normally would using the Biped Motion Panel and set its Key Info settings as you prefer. Next store that key for the currently selected track at the current frame into one of the six preset custom key icons using the set key icons with the black arrows.

Launching the CS3CustomKeys User Interface

The file named CS3Tools.cui is found in the 3ds max UI directory.

Choose Customize -> Load Custom UIà -> CS3Tools.cui

The user interface is displayed on the left side of the screen made with bitmapped buttons icons. You can also build a Tab Panel from scratch and manually add these icons by selecting from the Character Studio Tool category of the Customize UI Dialog.

See Macro Scripts and Defining Macro Scripts for additional details regarding Macro Scripts.

The icon buttons of CS3Tools.cui are displayed in the above image.

The bitmaps for the custom ui are installed into the 3ds max UI directory. See Creating Icon Bitmap Files for more information on creating and customizing icon bitmaps.

For access to Custom User Interface (CUI) files using MAXScript, see Custom User Interface Files. Note that the *.cui file format specification is not currently provided.

File Details

character studio 3 ships with the following Character Studio Tool files:

..\ui\CS3Tools.cui

..\Scripts\Startup\CS3Customkeys.ms

..\Scripts\Startup\CS3CustomKeys-presets.ini

..\Scripts\Startup\CS3convertBips.ms

..\Scripts\Startup\CS3Bip2BonesFloater.ms

..\Stdplugs\stdscripts\CS3CustomKeysPresetFloater.ms

Tutorial_Filename_CS3CustomKeysPresetFloater_ms bitmap files:

\3ds3.1\Cstudio\ui\Cstudio_16a.bmp, Cstudio_16i.bmp, Cstudio_24a.bmp, and Cstudio_24i.bmp.

Design Details

Custom Keys operate similar to channel buttons on a car radio. Anytime you "dial in" a set of parameters for a particular Biped key using the standard Biped "Key Info" rollout, you can store those values in one of the 6 custom key buttons available, by clicking on the associated tool bar icon.

All of the standard Biped Key Info parameters are supported, including: ease to, ease from, tension, continuity, bias, IK Blend value, IK body/space setting, and pivot point information. This can be seen by reviewing the struct Biped_key defined in ..\Scripts\Startup\CS3Customkeys.ms.

Custom Keys do not store transformation information (like a pose). Only Key Info attributes that affect the interpolation of a Biped's motion are stored. This allows stylistic settings to be easily transferred from frame to frame and even from Biped to Biped.

Each Custom Key is stored in a structure with the name Biped_key. Here is the definition of Biped_key:

struct Biped_key (type, ikblend, ikspace, easefrom, easeto, tension, continuity, bias, ikAnkleTension, balanceFactor, dynamicsBlend, ballisticTension)

Here is the initial Custom Key definition for the buffer "Preset Key A". Note that it is global and therefore visible in all running MAXScript code and will hold its value until 3ds max is exited.

Global keya_buffer = Biped_key type:#body ikblend:0.1 ikspace:1 easefrom:0.0 easeto:0.0 tension:25.0 continuity:0.0 bias:25.0 ikAnkleTension:.8 balanceFactor:1 dynamicsBlend:1 ballisticTension:0.5

The CS3Tools.cui has 13 icon buttons. The first 6 are "Set custom Biped Key *". The next 6 are "Change Preset * to current key". The last one is "Launch Preset Floater".

Pressing the 7th icon, "Change Preset A to current key", will change the value of the specified Preset A Key's keya_buffer to the value of the currently selected key. This will trigger the execution of the code:

macros.run "Character Studio Tool" "Change_Preset_Key_A"

The line above will be displayed in the MacroRecorder if the MacroRecorder is enabled.

This macro, "Change_Preset_Key_A", is defined in the file \3ds3.1\ui\macroscripts\Character Studio Tool-Change_Preset_Key_A.mcr and contains:

macroScript Change_Preset_Key_A

category:"Character Studio Tool"

toolTip:"Change Preset A to current key"

Icon:#("cstudio",7)

(

keya_buffer = CS3Change_Key_FN keya_buffer

CS3Save_presets()

)

See Macro Scripts and Defining Macro Scripts for additional details on creating and using Macro Scripts.

Select the last icon, "Launch Preset Floater", in the CS3Tools.cui. The Preset Floater dialog is launched and displayed. There are 6 "Change *" buttons and 6 "Show *" buttons. The 6 "Change *" buttons on the floating rollout perform the same actions as the 6 icon buttons on the CS Custom Key toolbar.

Pressing the "Change A" button executes the following code from the file ..\Scripts\Startup\CS3CustomKeys.ms

on ChangeA pressed do

(

keya_buffer = CS3Change_Key_FN keya_buffer

CS3Save_presets()

)

Pressing the "Show A" button will display the current contents of the keya_buffer as seen here:

Note that in both cases of Changing Preset A, the same function "CS3Change_Key_FN" was called. Additionally, notice that CS3Save_presets() is called anytime that a preset is changed. CS3Save_presets() will save all key values to a file called CS3CustomKeys-presets.ini that can be included in next launch of CS3Customkeys.ms. This is accomplished by Save_presets formating the presets as valid MAXScript code and using the include statement to include the code in CS3Customkeys.ms. The included code redefines the global key_buffers already defined.

The file \3ds3.1\Scripts\Startup\CS3Customkeys.ms Tutorial_File_name_CS3Customkeys_ms contains the following functions:

fn Set_One_Key_FN

fn Set_Key_FN

fn CS3Save_Presets

fn CS3Show_key_FN

fn CS3Change_Key_FN

fn ControllerOf

fn RootOf

The functions Change_Key_FN, Save_Presets and Set_Key_FN will be reviewed in more detail below.

As seen in the above examples of "ChangeA", the Change_Key_FN function is called with a single parameter, the particular local_key_buffer, and in the above examples with the keya_buffer. The local_key_buffer will either be returned unmodified or it will be returned modified.

The values for easeto, easeFrom, tension, continuity, bias, and ikAnkleTension are stored for all valid keytypes while ikblend and ikspace is stored for only #body keys. For #vertical keys, dynamicsblend and ballistictension are also stored. For #horizontal, balancefactor is also stored.

See BipedKey:MAXObject for more details.

The Change_Key_FN Code

The pseudo code for the Change_Key_FN is as follows:

function Change_Key_FN local_key_buffer

if nothing selected return local_key_buffer

if more than one selected return local_key_buffer

if not of class Biped_Object return local_key_buffer

get root node of the biped and controller for this body part

If body part is COM and trackselection not vertical, horizontal or turning then

print skipped and return local_key_buffer

else

save controller selection

if in figuremode return local_key_buffer

if in motion mode return local_key_buffer

if in footstepmode return local_key_buffer

if save controller selection not undefined then

set my_index to getkeyindex my_controller slidertime

if a key was found then

set my_key to biped.getKey my_controller my_index

if (my_key.type equals #body) then

(

set local_key_buffer.ikblend to my_key.ikblend

set local_key_buffer.ikspace to my_key.ikspace

set local_key_buffer.ikAnkleTensio to my_key.ikAnkleTension

)

if (my_key.type equals #vertical) then

(

set local_key_buffer.dynamicsblend to my_key.dynamicsblend

set local_key_buffer.ballistictension to my_key.ballistictension

)

if (my_key.type equals #horizontal) then

(

set local_key_buffer.balancefactor to my_key.balancefactor

)

set local_key_buffer.easefrom to my_key.easeto

set local_key_buffer.easeto to my_key.easefrom

set local_key_buffer.tension to my_key.tension

set local_key_buffer.continuity to my_key.continuity

set local_key_buffer.bias to my_key.bias

set return modified local_key_buffer

else

return local_key_buffer

else

return local_key_buffer

The actual function can be seen here:

FN CS3Change_Key_FN local_key_buffer  =

(

     if ($selection.count == 0 ) then

     (

          messagebox "No object selected.\nPlease select a Biped Object first.\n"

          return local_key_buffer

     )

     

     if ($selection.count > 1) then

     (

          messagebox "There are multiple objects selected.\nPlease select a single Biped Object first."

          return local_key_buffer

     )

     if ((classof $) != Biped_Object) then

     (

          messagebox "The selected object is not a Biped Object.\n Please select a Biped Object first\n."

          return local_key_buffer

     )

     

     my_root = $.controller.rootnode

     my_controller = $.controller

     

     if (my_root == $) then -- user is working on COM

     (

          if (my_controller.trackselection == 2) then

          (

               my_controller = my_controller.vertical.controller

          )

               

          else if (my_controller.trackselection ==1) then

          (

               my_controller = my_controller.horizontal.controller

          )

               

          else if (my_controller.trackselection == 3) then

          (

               my_controller = my_controller.turning.controller

          )

               

          else

          (

               msgtext = "'" + $.name  + "' skipped.\n"

               messagebox msgtext

               return local_key_buffer

          )

     )

     if (my_root.transform.controller.figuremode == true) then

     (

          messagebox "Change Preset Key is not supported in Figure Mode.\n Please exit Figure Mode first.\n"

          return local_key_buffer

     )

     

     if (my_root.transform.controller.motionmode == true) then

     (

          messagebox "Change Preset Key is not supported in Motion Flow Mode.\n Please exit Motion Flow Mode first.\n"

          return local_key_buffer

     )

     

     if (my_root.transform.controller.footstepmode == true) then

     (

          messagebox "Change Preset Key is not supported in Footstep Mode.\n Please exit Footstep Mode first.\n"

          return local_key_buffer

     )

     

     if (my_controller != undefined) then

     (

--          format "Controller: %\n" my_controller

          

          my_index = getkeyindex my_controller slidertime

--          format "Key Index = %\n" my_index

          if (my_index>0) then

          (

               my_key = biped.getKey my_controller my_index

               

--               format "key type is % \n" my_key.type

               

               if (my_key.type == #body) then

               (

                    local_key_buffer.ikblend = my_key.ikblend

                    local_key_buffer.ikspace = my_key.ikspace

                    local_key_buffer.ikAnkleTension = my_key.ikAnkleTension

               )

               if (my_key.type == #vertical) then

               (

                    local_key_buffer.dynamicsblend = my_key.dynamicsblend

                    local_key_buffer.ballistictension = my_key.balistictension

               )

               if (my_key.type == #horizontal) then

               (

                  local_key_buffer.balancefactor = my_key.balancefactor

               )

               local_key_buffer.easefrom =  my_key.easeto

               local_key_buffer.easeto = my_key.easefrom

               local_key_buffer.tension = my_key.tension

               local_key_buffer.continuity = my_key.continuity

               local_key_buffer.bias = my_key.bias

               

               return (local_key_buffer)

          )

     

          else

          (

               messagebox "Cannot change this preset because there is no key for the selected track at the current frame.\n Please advance the time slider to a frame where a key exists.\nOr select a track and frame where a key exists.\nOr set a key first.\n"

               return (local_key_buffer)

          )

     )

     else

     (

          messagebox "There is no controller for the selected track.\nYou may need to enable separate tracks.\n"

          return (local_key_buffer)

     )

)

CS3Save_Presets Code

-- *************************************************************

-- CS3Save_Presets - save all key values to a .ms file that can be re-read at next launch

-- *************************************************************

FN CS3Save_Presets =

(

     my_path = scriptspath + "startup\CS3CustomKeys-presets.ini"

     outfile = createfile my_path

     format "-- This file was created by the CS3 Custom Key Macro \"Save_Key_Presets\" to store user-customized presets for Biped custom key macro tools\n" to:outfile

     format "-- These values can be editted by hand for existing fields\n\n" to:outfile

     

     k = keya_buffer

     format "Global keya_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

     k = keyb_buffer

     format "Global keyb_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

     k = keyc_buffer

     format "Global keyc_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

     k = keyd_buffer

     format "Global keyd_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

     k = keye_buffer

     format "Global keye_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

     k = keyf_buffer

     format "Global keyf_buffer = Biped_key type:% ikblend:% ikspace:% easefrom:% easeto:% tension:% continuity:% bias:%\n" k.type k.ikblend k.ikspace k.easefrom k.easeto k.tension k.continuity k.bias to:outfile

          

     close outfile          

)

Set_Key_FN Code

The pseudo code for the Set_Key_FN is as follows:

function Set_Key_FN local_key_buffer

store state of shift key

if nothing selected then return false

if one object selected and not a Biped Object then return false

if local_key_buffer undefined then return false

-- process multiple selections independently so that we can

-- support key setting of multiple bipeds

else for each of the items in the selection

( if current item not a Biped Object then get next item

set my_root to the root of the hierarchy

if my_root is in figuremode then get the next item

if my_root is in motionmode then get the next item

if my_root is in footstepmode then get the next item

set my_controller to the item's controller

if hierarchy root is the current item then

set my_controller to the current track ( horizontal / vertical / turning)

else get the next item

if shift key was pressed then

(

for each key in my_controller

if key is selected set values for key

)

else

(

add a new key to my_controller at the current time and select the new key

get the index of the newly created key

if the index is greater than 0 then

get the newly created key

if (my_key.type == #body) then

(

set my_key.ikblend to local_key_buffer.ikblend

set my_key.ikspace to local_key_buffer.ikspace

)

set my_key.easeto to local_key_buffer.easefrom

set my_key.easefrom to local_key_buffer.easeto

set my_key.tension to local_key_buffer.tension

set my_key.continuity to local_key_buffer.continuity

set my_key.bias to local_key_buffer.bias

)

The actual function can be seen here:

FN Set_Key_FN local_key_buffer  =

(

if (keyboard.shiftpressed) then

keyboardshiftpressed_atstart = true

else

keyboardshiftpressed_atstart = false

     if ($selection.count == 0 ) then

     (

          messagebox "There are no objects selected.\nPlease select a Biped Object first.\n"

          return false

     )

     

    -- Showclass "Biped_" reports Biped_Object:GeometryClass{9125,0}

     if (($selection.count == 1) and ((classof $) != Biped_Object)) then

     (

          messagebox "The selected object is not a Biped Object.\nPlease select a Biped Object first\n."

          return false

     )

     

     if (local_key_buffer == undefined) then

     (

          messagebox "This preset key has not been initialized.\nPlease select a key first, and set its preset\n."

          return false

     )

     

--     process multiple selections independently so that we can support key setting of multiple bipeds

     

     else for i=1 to $selection.count do

     (

          if ((classof $selection[i]) != Biped_Object) then

          (

               msgtext =  "The selected object '" + $selection[i].name + "' is not a Biped Object.\nObject Skipped.\n"

               messagebox msgtext

               continue

          )

     

           my_root = $selection[i].controller.rootnode

          if (my_root.transform.controller.figuremode == true) then

          (

               msgtext = "'" + $selection[i].name + "' is in Figure Mode.\nSet Key is not supported in Figure Mode.\nPlease exit Figure Mode for this object first.\nObject Skipped.\n"

               messagebox msgtext

               continue

          )

          

          if (my_root.transform.controller.motionmode == true) then

          (

               msgtext = "'" + $selection[i].name + "' is in Motion Flow Mode.\nSet Key is not supported in Motion Flow Mode.\nPlease exit Motion Flow Mode for this object first.\nObject Skipped.\n"

               messagebox msgtext

               continue

          )

          

          if (my_root.transform.controller.footstepmode == true) then

          (

               msgtext = "'" + $selection[i].name + "' is in Footstep Mode.\nSet Key is not supported in Footstep Mode.\nPlease exit Footstep Mode for this object first.\nObject Skipped.\n"

               messagebox msgtext

               continue

          )

          -- find the root controller of this object

          -- my_controller = (controllerof ($selection[i]))

          

          my_controller = $selection[i].controller

          if (my_root == selection[i) then -- user is working on COM

          (

               if (my_controller.trackselection == 1) then

               (

                    my_controller = my_controller.horizontal.controller

               )

               

               else if (my_controller.trackselection == 2) then

               (

                    my_controller = my_controller.vertical.controller

               )

               

               else if (my_controller.trackselection == 3) then

               (

                    my_controller = my_controller.turning.controller

               )

               

               else

               (

                    msgtext = "'" + $selection[i].name  + "' skipped.\n"

                    messagebox msgtext

                    continue

               )

          )

          -- loop through all keys and call only if a key is selected

          if (keyboardshiftpressed_atstart == true) then -- user wants to process all selected keys in this track

          (

             found_selected_keys = 0

             for temp_index=1 to (numkeys my_controller) do

             (

                 if (iskeyselected my_controller temp_index) then

                 (

                    set_one_key_FN my_controller temp_index local_key_buffer -- set this key to the local key buffer

                    found_selected_keys += 1

                  )

              )

              if (found_selected_keys == 0 ) then

              (

                  msgtext = "Warning: No keys selected.\nNo changes made to " + $selection[i].name + "."

                  messagebox (msgtext)

               )

           else

               (

                   msgtext = "Changed " + (found_selected_keys as string) + " keys in " + $selection[i].name + "."

                   messagebox (msgtext)

                )

           )

           else -- (default) user wants to process just the current key

           (

              temp_index = getkeyindex my_controller slidertime

              -- format "index of key at this frame (temp_index) is %\n" temp_index

              if (temp_index == 0) then -- no key at this frame yet, let's create one...

             (

                 biped.addNewKey my_controller slidertime #select

                 temp_index = getkeyindex my_controller slidertime

              )

              -- temp_index should now indicate the ordinal index of the key for this track, 1st, 2nd, 3rd, etc.

              -- if it is still zero, then the user has selected a track or mode that prevents adding a key, like footsteps

              if (temp_index >0) then -- key was created ok

              (

                 set_one_key_FN my_controller temp_index local_key_buffer

              )

              else -- addnewkey failed, probably because the footstep track is selected

              (

                 msgtext =  "The selected track '" + ($selection[i]).name + "' does not support keys.\n Object Skipped.\n"

                 messagebox msgtext

              )

          )

     )

     

     return true

)

Naming the Buttons

You can name the key button accordingly to help you remember the purpose for that key type: "heel down", "finger point", "hand grab", etc.

To change the tooltip for the buttons on the CS3Tools.cui toolbar, right-click the button, choose "Edit Button Appearance", modify the text in the "Tooltip:" text box and click OK.

Extending the Number of Presets

The number of different settings that CS3Tools.cui supports can be extended. Two new macro files (*.mcr) for each new additional custom key, one for "Set" and one for "Change Preset", need to be added to the ..\ui\macroscripts directory and identical macros need to be added to ..\Scripts\Startup\CS3Customkeys.ms as well. Global key*_buffer's that matched the number of pairs of new macros added to the CS3Tools.cui need to be inserted into the file ..\Scripts\Startup\CS3Customkeys.ms Tutorial_File_name_CS3Customkeys_ms. Finally, the new macro files (*.mcr) have to be incorprated into the CS3Tools.cui. See Macro Scripts and Defining Macro Scripts for additional details.

See also

Biped MaxScript Extensions

Crowd MaxScript Extensions