How did I deal with…?

Programming an Automation Tool for Maya Using Python

The aim of this tool is to automate renaming and rigging processes for an XReality prop. The company (Misterspex) makes an augmented reality app for online glasses fitting. My tool provides the staff with automatic conventional nomenclature and rigs the assets with a button click.

Before and after hierarchy tool Before and after hierarchy tool
Comparing model and hierarchy in outliner before and after applying Hierarchy Tool.

After one pass of this automation tool, my rigged mesh is ready for testing in Babylon:

Hierarchy test in Babylon Hierarchy test in Babylon
Hierarchy test in Babylon.

These are the steps I'm taking to write this automation tool:

1. Planning the tool

My tool is building up a whole hierarchy with a button click. Swift requires the app to set the geometry on position and enable the temples' pivots to rotate accordingly. These schematics show what the general logic and nomenclature of this basic rig looks like:

Hierarchy tool schematics Hierarchy tool schematics
Hierarchy tool schematics.

This is what the outliner looks like before using the tool:

Viewport before tool Outliner before tool Viewport before tool Outliner before tool
Viewport and Outliner before applying hierarchy tool.

The staff is modeling hinges and temples only on one side (doesn't matter which one) for efficiency's sake. This is what the outliner looks like after renaming the pieces, using the tool's buttons:

Tool user interface Outliner after 1st method Tool user interface Outliner after 1st method
UI and outliner after 1st method.

The tool sends an error message if you try to repeat a name. After renaming:

  • Hinges' pivots are centered
  • Clicking on the “Create Hierarchy” button creates all groups
  • Sets the geometry at the correct position/orientation/scale
  • Makes a symmetrical copy of the temples, scales it, and
  • Sets the rotation pivots for the hinges.

2. Writing the automation tool in Python (Maya Commands)

After laying out the UI, these are the methods that I'm implementing to accomplish the tasks and link the buttons.

2.1. Selecting and calling methods

First of all, renaming the nodes:


def rename_component(component_name):
	component_to_rename = cmds.ls(selection=True)
	if cmds.objExists(component_name):
		cmds.error("Name already in use.")
	else:
		cmds.rename(component_to_rename, component_name)
		move_pivot_to_origin(component_name)
		cmds.delete(constructionHistory=True)
		cmds.makeIdentity(apply=True)
		cmds.select(clear=True)

Moving all pivots to 0,0,0 coordinates (Origin):


def move_pivot_to_origin(component_name):
	if component_name != 'Hinge_':
		cmds.move(0, 0, 0, ".scalePivot", ".rotatePivot", absolute=True)

Calling the functions from the “Create Hierarchy” Button:


def create_hierarchy():
	rename_after_side()
	select_side_geometry()
	duplicate_rename_side_geometry()
	copy_pivot()
	group_temple_after_side()
	creating_trim_group()
	moving_geometry_pivots_to_origin()
	grouping_all_nodes()
	scaling_all_by_10x()

lens_names = ['LensRight', 'LensLeft']
temple_group_names = ['TempleRight', 'TempleLeft']
temple_hinge_names = ['HingeRight', 'HingeLeft']
group_names = ['Frame', 'Lenses', 'Temple_', 'Temples']
side_names = ['Right', 'Left']

provisional_names = []
component_names = []
temple_components = []

2.2. Replacing method

The tool finds out on which side of the Z axis our hinge/temple are by:

  • Checking out the distance to Origin
  • Storing the information, and
  • Setting an underscore (_) as placeholder.

def rename_after_side():
	cmds.select(clear=True)
	cmds.select('Temple*', 'Hinge*', 'Lens*')
	temporal_group = cmds.group(name='temp_grp')
	temporal_group_duplicated = cmds.duplicate(cmds.ls(selection=True), returnRootsOnly=True)
	print temporal_group_duplicated
	temporal_group_duplicated += cmds.listRelatives(temporal_group_duplicated, allDescendents=True, fullPath=True)
	longNames = cmds.ls(temporal_group_duplicated, long=True)
	print longNames
	longNames.sort()
	side = '_'
	for n in longNames[::-1]:
		component_position = cmds.objectCenter(n, gl=True, local=True, x=True)
		if component_position < 0:
			side = 'Right'
		elif component_position > 0:
			side = 'Left'
		else:
			component_position = 0
		print side
		shortname = n.rpartition("|")[-1]
		cmds.rename(n, shortname.replace("_", side))
	cmds.ungroup('temp*')
	cmds.delete('Temple_*', 'Hinge_', 'Lens_')

Here you can see the meshes before applying the renaming method:

Meshes before renaming Meshes before renaming Meshes before renaming
Meshes and Outliner before renaming method.

2.3. Renaming and Adding Symmetry methods

After renaming the temples' geometry with the characters that my tool can select, and knowing on which side they are (left or right), the method:

  • Selects the geometry with the appropriate names
  • Makes a loop through the selection, and
  • Replaces the placeholders with the correct side name.

def select_side_geometry():
	cmds.select('Temple*', 'Hinge*', add=True)
	selected_children = cmds.ls(selection=True)
	cmds.group(empty=True, name='hub_grp')
	cmds.parent(selected_children, 'hub_grp')
	cmds.select('hub_grp')

def duplicate_rename_side_geometry():
	duplicated_group = cmds.duplicate(cmds.ls(selection=True), returnRootsOnly=True)
	print duplicated_group
	duplicated_group += cmds.listRelatives(duplicated_group, allDescendents=True, fullPath=True)
	longnames = cmds.ls(duplicated_group, long=True)
	print longnames
	longnames.sort()
	print longnames
	for n in longnames[::-1]:
		shortname = n.rpartition("|")[-1]
		if shortname.startswith('TempleRight' or 'HingeRight'):
			side = side_names[0]
			new_side = side_names[1]
		elif shortname.startswith('TempleLeft' or 'HingeLeft'):
			side = side_names[1]
			new_side = side_names[0]
		cmds.rename(n, shortname.replace(side, new_side))
	cmds.scale(-1, 1, 1)
	cmds.ungroup('hub*')

Here you can see all the pieces, with symmetry, at the right position and renamed after Z axis side:

Renaming after symmetry method Renaming after symmetry method Renaming after symmetry method
Renaming after symmetry method.

2.4. Groups and Pivots method

This method creates:

  • New parent groups
  • Finds out translation and orientation of the hinges' pivots
  • Copies the information
  • Resets pivots, except the temples group, and
  • Pastes the hinges pivots' information on them.

def copy_pivot():
	cmds.group(empty=True, name='TempleRight')
	cmds.group(empty=True, name='TempleLeft')
	hinges = ['HingeRight', 'HingeLeft']
	sides = ['TempleRight', 'TempleLeft']
	hinges_sides = zip(hinges, sides)
	for hinge, side in (hinges_sides):
		copy_pivot = cmds.xform (hinge, query = True, worldSpace = True, rotatePivot = True)
		cmds.parent(side, hinge)
		cmds.makeIdentity(side, apply = True, translate = True, rotate = True, scale = True)
		cmds.xform (side, worldSpace = True, pivots = copy_pivot)
		cmds.parent(side, world = True)
		print side

Here you can see the hinges' geometry with their pivots:

Pivots on hinges Pivots on hinges Pivots on hinges
Pivots on hinges.

2.5. Grouping Temples method

This method creates, names, and appends the corresponding geometry to the new groups.


def group_temple_after_side():
	cmds.select(clear=True)
	shapeList = cmds.ls(typ='mesh')
	cmds.select(shapeList)
	print shapeList
	transformList = cmds.listRelatives(shapeList, parent=True, fullPath=True)
	print transformList
	cmds.select(transformList)
	selection = cmds.ls(selection=True)
	print selection
	cmds.select(deselect=True)
	temple_groups = []
	temple_group = []
	for s in selection:
		if s.startswith('TempleRight') or s.startswith('HingeRight'):
			temple_group = 'TempleRight'
			cmds.parent(s, temple_group)
		if s.startswith('TempleLeft') or s.startswith('HingeLeft'):
			temple_group = 'TempleLeft'
			cmds.parent(s, temple_group)
		temple_groups.append(temple_group)

You can see the new groups in the outliner:

New groups for temples New groups for temples New groups for temples
New groups for temples.

2.6. Grouping Trims method

Swift needs the trim geometry inside a group to let the customer hide them behind the ears when online (© Misterspex).

Trimmed part from temples disappearing behind ear Trimmed part from temples disappearing behind ear
Trimmed part from temples disappearing behind the ear

This method looks for the named geometry, selects them and resets pivots.


def creating_trim_group():
	cmds.select(clear=True)
	cmds.select('TempleRight', hierarchy=True)
	cmds.select('TempleRight', deselect=True)
	selection = cmds.ls(selection=True)
	print selection
	for s in selection:
		if not s.endswith('Trim'):
			cmds.select(s, deselect=True)
		else:
			continue
	cmds.group(name='TrimRight')
	cmds.xform(zeroTransformPivots=True, worldSpace=True)
	cmds.delete(constructionHistory=True)
	cmds.makeIdentity(apply=True)

	cmds.select(clear=True)
	cmds.select('TempleLeft', hierarchy=True)
	cmds.select('TempleLeft', deselect=True)
	selection = cmds.ls(selection=True)
	print selection
	for s in selection:
		if not s.endswith('Trim'):
			cmds.select(s, deselect=True)
		else:
			continue
	cmds.group(name='TrimLeft')
	cmds.xform(zeroTransformPivots=True, worldSpace=True)
	cmds.delete(constructionHistory=True)
	cmds.makeIdentity(apply=True)

You can see the grouping in the Outliner after applying the method:

New groups for trims New groups for trims New groups for trims
New groups for trimmed portions at the end of the temples.

2.7. Setting Pivots method

This method resets all the pivots in temples, except for hinge group.


def moving_geometry_pivots_to_origin():
	cmds.select(clear=True)
	cmds.select(temple_group_names)
	cmds.group(name=group_names[3])
	cmds.select(clear=True)
	selected_children = []
	print selected_children
	cmds.select(clear=True)
	cmds.select('Temple*', 'Hinge*')
	cmds.select('TempleRight', 'TempleLeft', deselect=True)
	selected_children = cmds.ls(selection=True)
	print selected_children
	for c in selected_children:
		cmds.move(0, 0, 0, ".scalePivot", ".rotatePivot", absolute=True)

The picture shows that all the pivots are in the origin, except for the temples group:

All pivots in origin All pivots in origin All pivots in origin
All pivots in origin.

2.8. Finishing Up method

When you have everything renamed and sorted in the hierarchy, it means the time is come to set up the final groups, reset all pivots and transform nodes one last time.


def grouping_all_nodes():
	#group rest geometry
	cmds.group('Lens*', name=group_names[1])
	cmds.select(clear=True)
	cmds.select('Eyewires', 'Frame*', add=True)
	cmds.group(name=group_names[0])

	# group everything
	cmds.select(clear=True)
	cmds.select('Frame', 'Lenses', 'Temples', add=True)
	model_group = cmds.ls(selection=True)
	cmds.group(model_group, name='Model')

	# move pivot rest geometry to 0
	cmds.select('Eyewires', 'Frame*', 'Lens*', '*Trim', 'Trim*', add=True)
	selected_components = cmds.ls(selection=True)
	print selected_components
	for c in selected_components:
		cmds.move(0, 0, 0, ".scalePivot", ".rotatePivot", absolute=True)

	#delete history and freeze transformations on everything
	cmds.select('Model*', 'Frame*', 'Lens*', 'Eyewires', 'Temple*', 'Hinge*', '*Trim', 'Trim*', add=True)
	all_nodes =  cmds.ls(selection=True)
	for n in all_nodes:
		cmds.delete(constructionHistory=True)
		cmds.makeIdentity(apply=True, preserveNormals=True)
	

Now all geometry and groups match the schematics.

All geometry matching schematics All geometry matching schematics All geometry matching schematics
All geometry in outliner matching the schematics.

2.9. Scaling & Cleaning Up method

Swift needs to scale everything 10×:


def scaling_all_by_10x():
	cmds.select(clear=True)
	cmds.select('Model', hierarchy=False)
	cmds.scale(10, 10, 10)
	cmds.select('Model', hierarchy=True)
	cmds.delete(constructionHistory=True)
	cmds.makeIdentity(apply=True)
	cmds.select(clear=True)

Also note that I'm applying the tool before any polygon smooth nodes. The reason for this is that we are getting rid of the meshes' history, i.e. we cannot go back to the lowest poly version.

Scaled geometry Scaled geometry Scaled geometry
Geometry after last method.

This is an example of using programming and automation for Digital Content Creation (DCC). Click on these case studies if you want to see more examples of programming for real time, UI, and interaction:

Programming video game interaction with C++
Programming interaction

The C++ Interface is able to identify when the player picks up an item, adds to the counter, loops remaining and sends delegates when all collected.

Implementing Interface for video game with C++
Implementing C++ Interface

The C++ interface becomes active when overlapping items, sends delegates when the player clicks the “E” key, modifying the environment and other actors.

Delegates communicating UI and Gameplay
Delegates for UI & Gameplay

Using delegates sent by the interface to push text updates in the User Interface and trigger behaviors in the environment, like opening doors.

Adding Input Action from C++ into Unreal Engine
Adding Input Action in C++

Using the existing C++ third person character syntax to create an additional input action binding on UProperties, protected functions, and source.

Programming Gameplay with C++ for Unreal Engine
Programming Gameplay C++

Programming interaction for gameplay in C++ for Unreal Engine, using an interface, delegates and 2 bases for pickups and doors.

Also related:

Mentoring using video tutorials
Mentoring Video Tutorials

Series of tutorial videos for junior colleagues, training on modeling, rigging and texturing assets for an augmented reality app using Maya.

Modeling prop asset for XReality
Modeling Props Glasses

Modeling prop glasses assets for an augmented reality app. 3D scanning, adding retopology with Quad Draw in Maya, and setting up with Python tool.

Discover Technical Skills