Renaming objects in Maya (advanced)

 

[Download Script]

 

This is the third 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. In the second installment we evolved this tool to utilize an custom sorting method and improved the UI.

The previous system for sorting while functional limited our ability to do more sophisticated renaming. We're going to address these issues and introduce a neat system for managing object lists:

  • Ultimately we want to support an extended set of rename options (Prefix, Suffix, Find & Replace, exclusions) along with the standard rename and numeric incrementation

What's the goal exactly?

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

  • Stronger method for tracking objects and name changes through some form of 'object pointer list'
  • Remove the need to sort objects
  • Improve UI

What could go wrong?

  • Filtering objects can be tricky mainly due to the inherited nature of objects (i.e. mesh object is also a shape object)
  • Handling shape objects; Should shape objects be skipped? should the rename operation be set to ignore shapes?

 

Lets get started…


Utility procedures

 

padInt (Unchanged)

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.

 

 

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:

Renames and object and returns the long form result.

 

Details:

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.

 

 

newTransform

global proc string newTransform(string $name)
    {
        string $transformObj = createNode("-skipSelect", "-name", $name, "transform");
        refresh; // This is needed to finalize the createNode operation to stop the proc from becoming out of sync
        return $transformObj;
    }

Description:

 Creates a new transform object with the given name.

 

Details:

 I really, really, really (is that enough reallys?) dislike operations that change the selection (unless the operation is specifically intended to change the selection i.e. 'selectAllAnimatedObjects'). So I created this script to create a blank transform node with the '-skipSelect' option set to true.  This replaces the more common 'group -empty' command.

There seems to be some kind of sync bug however –  I'm guessing it has something to do with Maya spawning this operation off onto a seperate thread.  In anycase we need to add a 'refresh' call here to make sure Maya updates the scene before we continue.

Try commenting out the refresh and see what results you get from the script.  You'll find the objects rename correctly however the resulting object list returned from the core renameObjects function is blank??!  Some voodoo that's for sure, if anyone can shed some light on this I'd be very interested to hear.

 

cleanStringArray

global proc cleanStringArray(string $inputStringArray[])
    {
        string $cleanArray[] = {};
        for($eachItem in $inputStringArray)
        {
            if($eachItem != "") $cleanArray[size($cleanArray)] = $eachItem;
        }

        $inputStringArray = $cleanArray;
    }

Description:

Pretty quick and simple procedure that will step through a string array and remove any blank entries.

 

niceNameOf

global proc string niceNameOf(string $inputObj)
    {
        return(match("[^|]*$",$inputObj));
    }

Description:

Returns the last component of a object name string (everything after the last pipe "|" character).

 

Details:

Previously I was utilizing the 'shortNameOf' embedded script function however I found that this will return a long form name of an object if there are duplicately named objects in the scene.
So we really wanted the nice name version of the object in anycase.  This little match trick will give the result intended.

 

cullTrailingInts

global proc string cullTrailingInts(string $inputString)
{
    int $intCount = size(match("[0-9]*$", $inputString));

    return (substring($inputString, 1, size($inputString) - $intCount ));

}

Description:

 Again another simple procedure that will just remove the trailing numeric characters (0 to 9) from a string.
    i.e. "pCube33" becomes "pCube" 

 

adjustString

global proc string adjustString( string $baseString, string $prefix, string $suffix, string $find, string $replace)
{
    if($find == "") return ($prefix + $baseString + $suffix);
    return ($prefix + substituteAllString($baseString, $find, $replace) + $suffix);
}

Description:

A procedure that processes a list of string inputs into a resulting string.

 

Details:

This is a pretty key procedure that allows us to process all the base, prefix, suffix and find/replace options.

 

filterObjectsByTypes

global proc string[] filterObjectsByTypes(string $objects[], string $types[], int $evalShapeChildren)
{
    string $returnObjects[] = {};
    if(size($objects) == 0 || size($types) == 0) return $objects;
    for($eachObj in $objects)
    {
        int $matches = 0;
        for($eachType in $types)
        {
            $eachType = strip($eachType);
            if(objectType("-isAType", $eachType, $eachObj))
            {
                $matches = 1;
                break;
            }
            // This loop will be called if the evaluate shape children option is true
            // This way we're able to distinguish a transforms 'shape' type and process as needed
            if($evalShapeChildren)
            {
                string $shapeChildren[] = listRelatives("-fullPath", "-shapes", $eachObj);
               
                for($eachShape in $shapeChildren)
                {
                   
                    if(objectType("-isAType", $eachType, $eachShape))
                    {
                        $matches = 1;
                        break;
                    }                         
                }
            }
        }
        if(!$matches) $returnObjects[size($returnObjects)] = $eachObj;
    }
    return $returnObjects;
}

Description:

 Filters through a given list of objects and removes objects matching the types found in $types.  If $evalShapeChildren then each objects shape children are also evaluated for the filter even if they weren't directly selected.

 

Details:

This one is a little more tricky.  Here we're quering types of objects and their children to establish a desired list of objects.
The main reason it's tricky is that nodes inherit from other nodes and so you can get caught when an transform node's shape is a shape but also another type.

i.e. most objects are built by a shape object (i.e. camera, mesh, locator) parented to a transform and so if you just filtered out all transforms with shapes you would filter out most objects.

So through the $evalShapeChildres I can distinguish between directly included shape objects and those needing to be derived to establish the actual node type.

 

Utility: Object Pointer System

attrSize

global proc int attrSize( string $baseObject, string $attributeName)
	{
		return getAttr("-size", ($baseObject + "." + $attributeName));
	}

Description:

Gets the size of the given attribute

 

createObjectPointer

global proc string createObjectPointer()
	{
		global string $pointerObjString;
		string $groupObj = newTransform("__objPointer__");
		addAttr -ln $pointerObjString  -multi -at "message" $groupObj;
		return $groupObj;
	}

Description:

This creates a new transform object, assigns it a temp name "__objPointer__" and add the multi message attribute we're going to use to connect the objects to.

 

addObjectsToPointer

global proc addObjectsToPointer(string $objects[], string $pointer)
	{
		if (size($objects) == 0 || $pointer == "") return;
		global string $pointerObjString;

		for($i = 0; $i < size($objects); $i++)
		{
			connectAttr -f ($objects[$i] + ".message") ($pointer + "." + $pointerObjString + "[" + $i + "]");
		}
	}

Description:

Loops over the specified objects and one by one add them to the $pointer objects mutli message attribute.

 

Details:

Here we have the core method to maintaining the updated object list. This connects the 'message' attribute of each object to the multi message (message array) of another.
This connection is automatically updated whenever an object is deleted or renamed because the connection is an actual 'link' to the object rather then just a string containing the objects name.

 

getObjectFromPointer

global proc string getObjectFromPointer(string $pointer, int $index)
	{
		global string $pointerObjString;
		$pointer = longNameOf($pointer);
		string $returnObject = "";
		if (!attributeExists($pointerObjString, $pointer)) return $returnObject; // Attribute doesn't exist so return nothing
		string $listConnectionsResult[] = listConnections("-shapes", 1, "-connections", 1,
									($pointer + "."+ $pointerObjString + "[" + $index + "]"));
		return longNameOf($listConnectionsResult[1]);
	}

Description:

Basic procedure to fetch an object from a specific index in the given $pointer objects multi message attribute

 

getObjectsFromPointer

global proc string[] getObjectsFromPointer(string $pointer)
	{
		global string $pointerObjString;
		$pointer = longNameOf($pointer);
			string $returnObjects[] = {};
			if (!attributeExists($pointerObjString, $pointer)) return $returnObjects; // Attribute doesn't exist so return nothing
			int $attrSize = attrSize($pointer, $pointerObjString);

			// Scan attribute for all connected objects
			for($i = 0; $i < $attrSize; $i++)
			{
				string $listConnectionsResult[] = listConnections("-connections", 1,
											($pointer + "."+ $pointerObjString + "[" + $i + "]"));
			
				if ($listConnectionsResult[0] != "") // Skip blank entries
				{
					$returnObjects[size($returnObjects)] = longNameOf($listConnectionsResult[1]);
				}
			}

			return $returnObjects;
	}

Description:

This iterates over the $pointer objects multi message attribute and collects all connected objects.

 

Core procedures

 renameObjects (Modified!)

global proc string[] renameObjects(	string $objects[], string $newName, string $prefix, string $suffix,
						string $find, string $replace, string $typeMask, int $padding, int $cullTrailingInts)
	{
		
		string $filterTypes[] = {};
		tokenize($typeMask, ",; :/\|.-", $filterTypes);
		cleanStringArray($filterTypes);
		
		string $filteredObjects[] = $objects;

		$filteredObjects = filterObjectsByTypes($filteredObjects, {"shape"}, 0);

		$filteredObjects = filterObjectsByTypes($filteredObjects, $filterTypes, 1);

		string $pointerObj = createObjectPointer();
		addObjectsToPointer($filteredObjects, $pointerObj);
		string $newNames[] = {};
		string $originalNames[] = {};



		for($i = 0; $i < size($filteredObjects); $i++)
		{

			// Set object names to "__tempName__"
			$originalNames[$i] = niceNameOf($filteredObjects[$i]);
			renameLong(getObjectFromPointer($pointerObj, $i), "__tempName__");

			
			// Establish base name
			string $baseString = $originalNames[$i];
			if($cullTrailingInts) $baseString = cullTrailingInts($baseString);
			if($newName != "") $baseString = $newName;
			int $offsetIndex = 0;


			int $maxLoopCount = size(ls());


			// Generate new name from options
			// If padding is used then loop over name until a unique name is found
			// $maxLoopCount is used to detect a Infinate loop
			string $eachName = (adjustString($baseString, $prefix, $suffix, $find, $replace) 
						+ padInt($i, $padding));
			while(objExists($eachName) && $padding > 0)
			{
				$offsetIndex++;
				if ($offsetIndex > $maxLoopCount)
				{
					print ("Attempting to Undo..n");
					undo();
					delete $pointerObj;
					warning "Renamed failed! - Infinite loop detected!";
					return {};
				}
				$eachName = (adjustString($baseString, $prefix, $suffix, $find, $replace)
						+ padInt(($offsetIndex + $i), $padding));
			}

			$newNames[$i] = $eachName;
		}


		// Finally exectue the rename operations
		for($i = 0; $i < size($filteredObjects); $i++)
		{
			string $objectToRename = getObjectFromPointer($pointerObj, $i);
			renameLong($objectToRename, $newNames[$i]);
		}



		string $renamedObjects[] = getObjectsFromPointer($pointerObj);
		

		delete $pointerObj;

		print ("// ----------- Rename detailsn");

		for($i = 0; $i < size($filteredObjects); $i++)
		{
			print ("t" + $filteredObjects[$i] + "ntt-> " + $renamedObjects[$i] + "nn");
		}

		print ("// ----------- Success! Renamed " + size($renamedObjects) + " Objectsn");

		return $renamedObjects;

	}

Description:

As before this is the entry point for the rename routine and so it handles the main iteration over the specified objects processing them and renaming as needed.

 

Details:

Because of the new object message link system we don't need to keep track of or sort objects anymore. Without that worry we can process the given objects in the order they were passed to the procedure. While the method it self is a little more indirect the result is the increased simplicity of the rename operations.

However as you can see the procedure and it's arguments have become quite lengthy with all the added features and so becomes even more reliant on a solid UI to utilize the tool.

global proc string[] renameObjects(string $objects[], string $newName, string $prefix, string $suffix,
				string $find, string $replace, string $typeMask, int $padding, int $cullTrailingInts)

First off we have to parse the $typeMask string and break it up into each delinated type. The string will be 'tokenized' or seperated by each character found in the string ",; :/\|.-". So whereever there's one of those characters the tokenize function will create a new string element in the $filterTypes string array.

tokenize($typeMask, ",; :/\|.-", $filterTypes);

We then clean this array to remove any strings that are blank

cleanStringArray($filterTypes);

We dont want the script to attempt to rename the shape nodes of a transform as renaming the transform will update the shape node names automatically.
So we remove all shape nodes included in the object list.

string $filteredObjects[] = $objects;
        $filteredObjects = filterObjectsByTypes($filteredObjects, {"shape"}, 0);

Then we remove all user specified 'types' from the list of objects

$filteredObjects = filterObjectsByTypes($filteredObjects, $filterTypes, 1);

Now we've got a clean array of objects lets create the pointer holder object and add the objects to it

string $pointerObj = createObjectPointer();
        addObjectsToPointer($filteredObjects, $pointerObj);

The inital part of the first loop is used to reset the names of the objects we plan to rename to something that wont clash with the specified new names.  We need to rename the objects to some kind of temp name before we do the actual final rename so we can 'reuse' object names that might already be assigned to objects in the current target list of objects.

renameLong(getObjectFromPointer($pointerObj, $i), "__tempName__");

So without this if you had a list of objects:

{"pCube03", "pCube01", "pCube04"}

and you attempt to rename them to the base name of "pCube" with a padding of 2 you'll 'clash' with the existing objects.

So as a result you'd get something like:

{"pCube02", "pCube05", "pCube06"}

Rather then the desired:

{"pCube01", "pCube02", "pCube03"}

Note: I'm using the 'getObjectFromPointer' procedure here rather then just quering the $filteredObjects array.  This is because as soon as any rename or delete operations are executed on the objects the $filteredObjects array, which is uses full paths to the objects, will become invalid.

After safely assigning new names to these objects the next step is to find the best fit unique object name for each object. If padding is set it will loop until it finds a unique scene name if not it'll just assign the new name and let Maya's internal name clashing systems handle incrementation.

There's a safty mechanism built into the while loop to detect an infinate loop.  Allthough this method is pretty safe it's still helpful to have some form of infinate loop protection – espicially when you're writing and debugging the script.

This $maxLoopCount is set to the total number of nodes in the scene – a slighty arbitary metric but accurate measure of an infinate loop in this while instance.

int $maxLoopCount = size(ls());

Therefore we can test if the loop count becomes greater then the total number of nodes in the scene and bail out of the rest of the script with an error letting the user know it failed.

if ($offsetIndex > $maxLoopCount)
            {
                print ("Attempting to Undo..n");
                undo();
                delete $pointerObj;
                warning "Renamed failed! - Infinite loop detected!";
                return {};
            }

The second loop will complete the actual rename operation.

for($i = 0; $i < size($filteredObjects); $i++)
            {
                string $objectToRename = getObjectFromPointer($pointerObj, $i);
                renameLong($objectToRename, $newNames[$i]);
            }

Now the rename operations are all completed we can update the $renamedObjects variable with the new full path object strings

string $renamedObjects[] = getObjectsFromPointer($pointerObj);

After some cleanup and debug print statements we return the new list of objects

return $renamedObjects;
 

  

UI procedures

 renameObjectsUI (Modified!)

global proc renameObjectsUI()
	{


		global string $renameObjectsWindow;
		if (window("-exists", $renameObjectsWindow)) deleteUI -window $renameObjectsWindow;
		$renameObjectsWindow = window(
						"-widthHeight", 306, 281,
						"-resizeToFitChildren", 1,
						"-sizeable", 0,
						"-title", "Rename Objects Advanced",
						"renameObjectsWindow");
		
			columnLayout;

					rowColumnLayout -width 308 -numberOfRows 9;

									
						string $prefixCtrl = textFieldGrp(
										"-label", "Prefix",

										"-ann", "Prefix to final object names",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");

						string $newNameCtrl = textFieldGrp(
										"-label", "New Name",
										"-ann", "Override existing names with this string",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");

						string $suffixCtrl = textFieldGrp(
										"-label", "Suffix",
										"-ann", "Suffix to final object names",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");
						string $findCtrl = textFieldGrp(
										"-label", "Find",
										"-ann", "String to search for an replace",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");
						string $replaceCtrl = textFieldGrp(
										"-label", "Replace",
										"-ann", "What to replace the 'found' string with",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");
						string $typeMaskCtrl = textFieldGrp(
										"-label", "Exclude Types",
										"-ann", "Enter types of objects to exclude seperated by commas: i.e. camera, locator",
										"-cw", 1, 90,
										"-cw", 2, 208,
										"-text", "");
						string $paddingCtrl = intFieldGrp(
										"-numberOfFields", 1,
										"-label", "Padding",
										"-ann", "How much to pad integer suffix. 0 relies on Maya's default incrementing",
										"-cw", 1, 90,
										"-cw", 2, 30,
										"-value1", 0);
						string $cullIntsCtrl = checkBoxGrp(
										"-numberOfCheckBoxes", 1,
										"-cw", 1, 90,
										"-value1", 0,
										"-ann", "Delete trailing integers at the end of objects before renaming",
										"-label", "Clean Ints");


					setParent..;



					rowColumnLayout -numberOfColumns 2
					-columnWidth 1 150
					-columnWidth 2 150;

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

					rowColumnLayout -numberOfColumns 1
					-columnWidth 1 300;

						button -l "Apply (Selection)"	-c (	"renameObjects("
											+	"getSelection(),"
											+	"("" + textFieldGrp("-q", "-text", "" + $newNameCtrl +"")), "
											+	"("" + textFieldGrp("-q", "-text", "" + $prefixCtrl +"")), "
											+	"("" + textFieldGrp("-q", "-text", "" + $suffixCtrl +"")), "
											+	"("" + textFieldGrp("-q", "-text", "" + $findCtrl +"")), "
											+	"("" + textFieldGrp("-q", "-text", "" + $replaceCtrl +"")), "
											+	"("" + textFieldGrp("-q", "-text", "" + $typeMaskCtrl +"")), "
											+	"(intFieldGrp("-q", "-value1", "" + $paddingCtrl + "")), "
											+	"(checkBoxGrp("-q", "-value1", "" + $cullIntsCtrl  + ""))"
											+");")
											-ann	"Do rename!";

					setParent..;
					string $form = formLayout("-numberOfDivisions", 100);
					string $website = text(
								"-width", 300,
								"-enable", false,
								"-font", "smallFixedWidthFont",
								"-bgc", 0.1, 0.2, 0.3,
								"-align", "center",
								"-label", "www.rodgreen.com");

					formLayout -edit
					    -attachForm     $website     "top"    -4
					$form;

		showWindow $renameObjectsWindow;

	}

Description:

Front end for the rename objects tool.

 

Details:

Most of this is pretty standard UI stuff – built of and extended on the previous UI procedure. The thing to note is the assigning of the controls to a variable so that we can include them in the 'Apply' button's command.

It's also important to stress that because this UI is the only real intuitive way to use the rename tool it needs to be clean and fast.  The use of annotation strings ("-ann") give a extra little bit of help to users confused about the usage of certain options.

 

Thats it!

 [Download Script]

Conclusions / next steps?

 

  • Improving the script beyond this would involve adding more and more features to the rename options and UI (i.e. saving common or user specified rename options) however the underlying system would support any arbitrary object name change.
  • The method of utilizing a transform node's message array attribute can help in many other tools not just renaming.  This could also be used to maintain connections within rigs (i.e. for dynamic constraints), give more explicit connections for exporting to a game engine, manage dynamic systems like destruction, etc.

 

I hope you found this informative and at the very least gave you some alternative ways to think about how to handle objects and their names in Maya.

Thanks for reading!

22 thoughts on “Renaming objects in Maya (advanced)”

  1. hmm. The attrSize is not included in the script – I copied it from above and put it in there.

    But in the end it didn’t work for me unfortunately. The node names are still not unique in my sample scene. I’m not sure if its because of instancing or what – it renamed 701 nodes so there is a lot to look at.

    A good check would be to see if the new node name is the same as the old node name, and if it is then don’t rename it. At first I tried to use this utility as a “uniqueifier”, so I gave no prefix, suffix, or anything. I was surprised when the output was 701 nodes renamed to the same name.

    Overall though, it very nice and helpful work to build off, and will likely save me days perhaps weeks of time when all is said and done. Thanks!

  2. If you use ‘0’ padding value it will rely on Maya’s internal systems for automatic naming (which only adds an integer if there’s duplicate nodes on the same level of the hierarchy).

    However if you use a value of ‘1’ it will make 100% sure that all nodes are unique… I tested this with over 20000 nodes for speed and reliability.

    Thanks for the heads up regarding the missing attrSize… I’ll add it now.

    Let me know if the padding helps.

  3. Hi Rod,

    It seems like the 1 padding is creating unique nodes, but the script I run afterwards is still crashing with a “more than one node matches” error. Hmm. Its probably on my side. I’ll have to dig through it this weekend.

    Does anyone else think that every function should return the full path to the node unless explicitly told otherwise in this terrible mess of a program that is Maya? Grr.

    btw, checked out the Project Offset link at the bottom. WOW. Thats really cool stuff!

  4. A M A Z I N G ! ! !

    you R a LifeSaver with this script and UI –
    Just renamed 2000 Objects From a imported file that had bad Prefix

    successssss

    thank you!!!

  5. Hi, I have no experience in MEL-scripting whatsoever. But I do have a suggestion to this ingenious script. Let’s say I want to find and replace several object names at once, like object12, object13 and object14, but not object15, object16 etc. For this I need the ability to find more than one name at once, which could be done by separating each word with a comma (,).

  6. I just realised I can do the filtering by manually selecting the right objects, which probably even goes faster in most cases than the solution that I proposed.

    Thanks a thousand for this script though! It will save me lots of frustration in many projects to come I’m sure.

  7. Is there a way to start the incrementing @ 1 instead of 0? I tried changing the $offsetIndex on line 200 from:
    $offsetIndex = 0;
    to
    $offsetIndex = 1;
    Thanks.
    -scott

  8. Hey I updated it with a new option .. 'Index Offset' which will offset the starting value of the naming from a specific index.. i.e. "1"

  9. Hi,
     
    I don't understand much of scripting in Maya, but I think this script may be the solution to my problem. I've got a huge object-tree from a CAD file, with a lot of objects with the same name (but in different groups). I want to rename all the object into a unique name. Can somebody explain to me how I can achieve this?

  10. Dude this script is freaking awesome.
    all I wanted to do was just to export meshes to UVlayout via Maya plugin but I can't because each time imports geometry it has prefixes that contained a colon(":") I thought I would be insane were I to rename all those objects one by one
    windows 7 64x
    maya 2012 64x

  11. Thank you so much!
    This script is a time saver and your tutorial/explanations are clean and easy to follow. Very clean code, sir.
    Thanks again!

Leave a Reply to Truce Cancel reply

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