Renaming objects in Maya (intermediate)

 

[Download Script]

 

This is the second part to a three part tutorial on renaming objects in Maya.  In the first installment we established a collection of procedures and a simple UI that allows multiple objects to be renamed.

Some issues were identified with the methods used previously and so in this next stage I'm going to evaluate the procedures and improve the script. Specifically this tutorial will address the following issues:

  • The previous script is a handy quick renamer but doesn’t always produce unique names for all objects
  • Numerical increments with good padding options
  • The UI was over simplified and could be improved with more options

What's the goal exactly?

We need to advance the existing tool to support the following:

  • Sort the list of nodes using a more robust Hierarchy sort method
  • Add  support for incrementing names with padding ( i.e. '001' rather then just '1')
  • Improve UI

What could go wrong?

  • Since we are writing our own sorting method we need to make sure it's fast
  • Manually incrementing object names could prove difficult with renaming hierarchy's of similar names

 

Lets get started…


Utility procedures

 

getSelection (Unchanged)

global proc string[] getSelection()
{
	return (ls("-selection", "-long", "-flatten"));
}

Description:

While none of my core or utility procedures rely on switching and querying selection I do a lot of interfacing with the user through their selection. So a unified selection query procedure is important. The long and flatten flags returns the objects with long paths and flattened individualized list respectively.

i.e.

 

{"locator1", "pCube1.uv[0:2]"}

 

would return as

 

{"|pCube1|pCube2|locator1", "|pCube1.uv[0]", "|pCube1.uv[1]", "|pCube1.uv[2]"}

 

renameLong (Unchanged)

global proc string renameLong(string $object, string $newName)
{
	return longNameOf(rename($object, $newName));
}

Description:

As the default rename procedure doesn't always return the long version of the object we'll create this wrapper procedure to ensure it does.

 

stringArrayFind (Unchanged)

global proc int stringArrayFind(string $item, string $array[])
{
    for($i = 0; $i < size($array); $i++) if($array[$i] == $item) return $i;
    return -1;
}

Description:

Pretty routine procedure that returns the first index of a given 'item' in a string array.

 

Details:

This procedure will be called constantly throughout and therefore should be as efficient as possible.

 

getStringArrayMapping (Unchanged)

global proc int[] getStringArrayMapping(string $unmodified[], string $modified[])
{
    int $returnArray[] = {};

    string $searchList[] = $unmodified;

    for($i = 0; $i < size($modified); $i++)
    {
        int $foundIndex = stringArrayFind($modified[$i], $searchList);
        $returnArray[$i] = $foundIndex;
        $searchList[$foundIndex] = "";
    }

    return $returnArray;
}

Description:

This procedure does a nice little trick of giving you the index mapping between $unmodified and $modified string arrays.  What this basically means is it gives you how the order of strings has changed between the two given arrays.

Why is this needed?

If you reorder an array of objects there's no way to tell what order they were originally in and thus destroying the users specific selection order.  So we create a list of indexes that provide a 'mapping' between the original list and the modified list.

i.e.

If you sent this procedure a string array

$unmodified[] = {"dog", "mouse", "cat"}

and a string array

$modified[] = {"mouse", "cat", "dog"}

This procedure would return the index (int) array

$returnArray = {1, 2, 0}

as index '1' represented "mouse", index '2' represented "cat" and index '3' represented "dog" in the $unmodified array.

 

Details:

The following line is added for performance. Setting the found index to a zero length string or "" will speed up the string comparison inside the 'stringArrayFind' procedure:

$searchList[$foundIndex] = "";

 

padInt

global proc string padInt(int $inputInt, int $length)
{
    string $returnString = $inputInt;

    for($i = size($returnString); $i < $length; $i++) $returnString = ("0" + $returnString);

    return $returnString;
}

Description:

This will take the input integer value and pad it with preceding zeros so that it fills a certain text length.

 

Details:

If the $inputInt is longer in string length then the specific $length it will return the original int as a string.

 

countCharInString

global proc int countCharInString(string $char, string $string)
{
	int $count = 0;
	for($i = 1; $i <= size($string); $i++) if(substring($string, $i, $i) == $char) $count++;
	return $count;
} 

Description:

Counts the number of times $char appears in the given string ($string)

 

intArrayFloor

global proc int intArrayFloor(int $intArray[])
{
    int $returnInt = $intArray[0];
    for($eachInt in $intArray) if($eachInt < $returnInt) $returnInt = $eachInt;

    return $returnInt;
}

Description:

Returns the smallest value in the given int array ($intArray)

 

sortStringByInts

global proc string[] sortStringByInts(string $stringArray[], int $intArray[])
{

	string $returnArray[] = {};
	int $arrayToSearch[] = $intArray;
	int $loopCount = size($arrayToSearch);

	int $floor = intArrayFloor($intArray);
	
	for($retIndex = 0; $retIndex < $loopCount; $retIndex++)
	{
		int $iMax = $floor;
		int $indexFound = -1;
		for($i = 0; $i < $loopCount; $i++)
		{
			int $eachInt = $arrayToSearch[$i];	
			if( $eachInt >= $iMax )
			{
				$iMax = $eachInt;
				$indexFound = $i;
			}
		}

		$arrayToSearch[$indexFound] = ($floor - 1);
		$returnArray[$retIndex] = $stringArray[$indexFound];


	}

	return $returnArray;
	
}

Description:

This will step through a given string array ($stringArray) and sort that array based on the integer values found in $intArray.

 

Details:

The return array is sorted  in a descending order

sortStringByInts({"dog", "mouse", "cat"}, {2,0,1});

would return

{"dog", "cat", "mouse"}

 

sortObjectsByHierarchy 

global proc string[] sortObjectsByHierarchy(string $objectArray[])
{
    int $ints[] = {};
    for($eachObj in $objectArray) $ints[size($ints)] = countCharInString("|", $eachObj);

    return sortStringByInts($objectArray,$ints);
}  

Description:

 Primarily a wrapper procedure that collects the hierarchy depth for each object (via 'countCharInString') and passes that along with the object array to the 'sortStringByInts' procedure.

Details:

 The "|" character that is passed to 'countCharInString' procedure represents a 'level' in the hierarchy.  The way Maya represents a long path of an object is by listing out all parents in order distguishing each object with a "|" or pipe character.

i.e. '|pCube1|pCube2|locator1'

which translates to 'locator1' being a child of 'pCube2' which is a child of 'pCube1' which in turn is a child of the world.

 

 

Core procedures

 

renameObjects (Modified!)

global proc string[] renameObjects(string $unsortedObjs[], string $newName, int $padding)
{

    string $sortedObjs[] = sortObjectsByHierarchy($unsortedObjs);
    int $mapping[] = getStringArrayMapping($unsortedObjs, $sortedObjs);
    int $startIndex = 0;
    string $newNamesUnsorted[] = {};
    for($i = 0; $i < size($unsortedObjs); $i++)
    {
        string $eachName = ($newName + "_" + padInt($startIndex + $i, $padding));
        while(objExists($eachName))
        {
            $startIndex++;
            $eachName = ($newName + "_" + padInt($startIndex + $i, $padding));
        }
        $newNamesUnsorted[size($newNamesUnsorted)] = $eachName;
    }

    for($eachIndex in $mapping)
    {
        $unsortedObjs[$eachIndex] = renameLong($unsortedObjs[$eachIndex], $newNamesUnsorted[$eachIndex]);
    }

    return $unsortedObjs;
} 

Description:

This is the entry point for the rename routine and so it handles the main iteration over the specified objects.

 

Details:

Here we have our custom built hierarchy sort routine.

string $sortedObjs[] = sortObjectsByHierarchy($unsortedObjs);

 

Lets pass this newly sorted list through our string array mapper and see how it was reordered:

int $mapping[] = getStringArrayMapping($unsortedObjs, $sortedObjs);

 

Now we have the mapping we can step through the mapping and process each object collecting the new name for each object and adding it to the $newNamesUnsorted string array.

for($i = 0; $i < size($unsortedObjs); $i++)

 

This creates a string to use for the objects new name.  It overrides the objects name with the $newName along with given increment options

string $eachName = ($newName + "_" + padInt($startIndex + $i, $padding));

 

 This double checks that the new name we are going to assign to the object is unique in the scene (at all levels in the hierarchy)

while(objExists($eachName))
        {
            $startIndex++;
            $eachName = ($newName + "_" + padInt($startIndex + $i, $padding));
        }

Then rename the object at the identified index and reassign it back into the original $unsortedObjs array:

 $unsortedObjs[$eachIndex] = renameLong($unsortedObjs[$eachIndex], $newNamesUnsorted[$eachIndex]);

 

UI procedures

 

renameObjectsUI (Modified!)

global proc renameObjectsUI()
{


    global string $renameObjectsWindow;
    if (`window -exists $renameObjectsWindow`) deleteUI -window $renameObjectsWindow;
    $renameObjectsWindow = `window -widthHeight 308 100 -sizeable 0 -title "Rename Objects Intermediate" "renameObjectsWindow" `;
   
        columnLayout;

                rowColumnLayout -numberOfRows 4;

               
                    string $paddingCtrl = `intFieldGrp -numberOfFields 1
                                        -label "Padding"
                                        -ann "How much to pad integer suffix"
                                        -cw 1 90
                                        -cw 2 30
                                        -value1 2`;

                    string $newNameCtrl = `textFieldGrp
                                    -label "New Name"
                                    -ann "Enter new name to rename object(s) to"
                                    -cw 1 90
                                    -cw 2 208
                                    -text "myObject"`;



                setParent..;



                rowColumnLayout -numberOfColumns 4
                -columnWidth 1 100
                -columnWidth 2 100
                -columnWidth 3 100;

                   
                   
                    button -l "Cancel" -c "deleteUI -window $renameObjectsWindow;";
                    button -l "Defaults" -c "renameObjectsUI();"
                        -ann "Reset settings to defaults";

                    button -l "Apply"    -c (    "renameObjects(getSelection(),"
                                +    "("" + textFieldGrp("-q", "-text", "" + $newNameCtrl +"")), "
                                +    "("" + intFieldGrp("-q", "-value1", "" + $paddingCtrl + "")));")
                                -ann    "Do rename!";

                setParent..;

    showWindow $renameObjectsWindow;

} 

Description:

 The start of a UI for renaming select objects in a scene. Albeit still simple the structure of this procedure is easy enough to extend.

 

Thats it!

[Download Script]

Conclusions / next steps?

 

  • The object list that's returned from the renameObjects isn't accurate if objects in a hierarchy are renamed
  • The method of referencing and updating the list of objects we are handling isn't strong enough
  • Provide object type filtering options
  • The current method for setting the objects name is destructive without any options for just adding a prefix or suffix
  • Search and replace through selected objects or whole scene

 

Check out the next stage of this tool in "Renaming objects in Maya (advanced)" – Coming soon!

Leave a Reply

Your email address will not be published. Required fields are marked *