Home Website Youtube GitHub

Pre Custom script that cleans the scene

Hi, I keep catching myself having to select all the geo, rig and joint - deleting them just to I can apply my changes to the guide.
So I looking to create a Pre custom script that basically deletes everything on the scene but keeps the guide every time I need to rebuild.

I’m current running a “clean_scene.py” as a PRE custom script that I think I got from @chrislesage I think some time ago:

import mgear.shifter.custom_step as cstp
import maya.mel as mel
import pymel.core as pm

class CustomShifterStep(cstp.customShifterMainStep):
	def __init__(self):
		self.name = "clean_scene"

	def run(self, stepDict):

		# Delete all ngSkin nodes
		# Delete all controller tags

		# delete unused nodes

I was thinking on adding this to it:


but this also deletes the guide nurb curves…and I don’t want that haha.

can someone help me? I’m sure I’m not the only person who want to automate cleaning a scene so I can do a clean guide re-build

Do you delete your geo and import it fresh each build? Interesting. I always keep my geo.

pm.ls(type='mesh') should only return polygon meshes. Do you have any other code running that you aren’t showing?

I would suspect MLdeleteUnused is actually causing the problem. That command might cause some different results depending on your Maya settings. I would never run that, personally. I don’t trust Maya to do automatic things to my scene. Try commenting out that line and seeing if your nurbs curves still disappear.

In your “Optimize Scene Size” settings, I bet you have “Remove unused NURBS curves” checked, right? And that might add an environment variable that makes the MEL code also delete your NURBS curves, unless you specified the exact flags in your command. Be so extremely careful with Optimize Scene Size… Especially on built rigs.

Also, you’d actually want to delete the transforms, not the meshes.

# If you aren't familiar with this, [x for x in foo] is "List Comprehension".
allGeo = [mesh.getTransform() for mesh in pm.ls(type='mesh')]

And actually, I assume you’d want to delete the top group of your geo, and not the individual geometry transforms. Right? You can either have a naming convention and always delete “geo_grp”. Or you can get the root of the objects. Unfortunately, I think there is a PyMEL bug in Maya 2020, where node.root() causes an error if an object doesn’t have a parent. So I wrote a hacky function. In Maya 2018, you can just use node.root() and it returns the top-most parent.

import pymel.core as pm

def get_root(node):
    '''Hack because Maya 2020 fails when querying a node.root() node.'''
    # Use the pipes to know how many levels deep to measure the hierarchy
    level = node.longName().count('|')
    # Add level + 5 to be safe and count a few extras.
    roots = [node.getParent(ii) for ii in range(level+5) if node.getParent(ii)] or []
    if roots:
        return roots[-1]
        return []

# 1. Get all geo
allGeo = [mesh.getTransform() for mesh in pm.ls(type='mesh')]
# 2. Get all top root parents of all geo. Use a "Set Comprehension" {} instead of List Comprehension to filter out all duplicates.
geometryGroups = {get_root(geo) for geo in allGeo}
# 3. Filter out stuff we don't want to delete. The guide has an attribute guide.ismodel.
# Include any other criteria you want in this List Comprehension
safeToDelete = [
        grp for grp in geometryGroups
        if not grp.hasAttr('ismodel')
        if not grp.name() == 'guide'
        if not grp.name() == 'controllers_org'
        if not grp.hasParent('guide')


Thanks Chris. You are right I don’t always need to delete the Geo, but I do like to do it just to make sure I’m always working on the latest version - but toggle it off the delete/import geo pre script when I don’t want to.
I do always want to delete the rig group and the joints (I have a game rig, and my joint tree is on the outside in maya root)

I think I was probably doing something weird that was deleting my curves. Not happening anymore!
I do have the issue you mention with maya not deleting the top group of the mesh.
This is great and solves it for me in Maya 2019! many thanks :slight_smile:

allGeo = [mesh.getTransform() for mesh in pm.ls(type='mesh')]

is there any other scripts that you use to make sure the scene is clean before rebuilding?

This is some of the stuff I run. I omitted some pretty specific stuff I do to our rigs.

I also export a text file of a list of the pre and post scripts, so I can debug things without opening the scene.

import pymel.core as pm
import os

# Turn off preserve children if you forgot to!
import maya.mel as mel
mel.eval('setTRSPreserveChildPosition false;')

def fix_double_index_names():
    for i in range(20):
        for each in pm.ls('*_L{0}_L{0}_*'.format(i)):
        for each in pm.ls('*_R{0}_R{0}_*'.format(i)):
        for each in pm.ls('*_C{0}_C{0}_*'.format(i)):

def set_eye_aim_pos():
    # Set the eye aim positions. No need for a human to do this.
    midEyeAim = pm.PyNode('eyeAim_C0_root')
    eyeHook = pm.PyNode('eyeHook_L0_root')
    oPos = eyeHook.getTranslation(space='world')
    oPosMid = midEyeAim.getTranslation(space='world')
    oPosMid.y = oPos.y
    midEyeAim.setTranslation(oPosMid, space='world')
    for side in 'LR':
        eyeHook = pm.PyNode('eyeHook_{}0_root'.format(side))
        eyeAim = pm.PyNode('eyeAim_{}0_root'.format(side))
        oPos = eyeHook.getTranslation(space='world')
        oPosMid = midEyeAim.getTranslation(space='world')
        oPos.z = oPosMid.z
        eyeAim.setTranslation(oPos, space='world')

def delete_existing_sets():
    # Delete any existing objectSets. They can sometimes persist if any custom rigging in the guide
    # exists when deleting and rebuilding the rig. Or when extracting controls in mGear.
    pm.delete(pm.ls('*_grp*', type='objectSet'))

def output_pre_post_scripts():
    ''' Export the pre and post script attributes to text files
    This is so I can easily debug the state of a character's scripts '''
    ### OUTPUT: Just split the lines and write them to text.
    #TODO: I probably want to get rid of the "name |" from the start, and just have the path.
    guide = pm.PyNode('guide')
    charName = guide.rig_name.get()

    postpath = os.path.abspath(os.path.dirname(__file__))
    basepath = os.path.abspath(os.path.join(postpath, '../../../..'))
    pathTemplate = r'data/characters/{}/configs/{}_scripts_{}.txt'
    prePath = os.path.join(basepath, pathTemplate.format(charName, 'pre', charName))
    postPath = os.path.join(basepath, pathTemplate.format(charName, 'post', charName))
    preText = guide.preCustomStep.get().split(',')
    postText = guide.postCustomStep.get().split(',')
    with open(prePath, "w") as myfile:
    with open(postPath, "w") as myfile:


And here are nodes I clean. Nothing too comprehensive. But this does the trick for me.

### Delete all tag controllers. They build up the file size over time.

### Delete all ngSkin nodes. If you delete the rig, they remain and cause large file sizes.
#NOTE: This means that you must not use ngSkin layers in your rig guide!!! You can use them in your built rig, of course.


# These nodes inflate the scene size and cause the scenes to take WAY longer to open.
# (20 seconds instead of 0.9 seconds to open the model files, for example.)

# For some reason, sometimes dagPoses get polluted into the guide scene.
# I *think* it would be safe just to delete all dagNodes, but just in case, I'm only deleting ones who
# are on the buffer controls under controllers_org. Someone might choose to custom rig a hack inside a guide on purpose.
buffers = pm.PyNode('controllers_org')
for each in pm.ls(type='dagPose'):
    # check if the transform inputs of the dagPose are the buffer controls.
    if any([x.hasParent(buffers) for x in each.inputs(type='transform')]):

This is great addition for my custom steps, thanks!