Skip to content

Instantly share code, notes, and snippets.

@JustinPedersen
Created May 17, 2023 07:42
Show Gist options
  • Save JustinPedersen/4f4d73eee27a3cea7612275a5376ad96 to your computer and use it in GitHub Desktop.
Save JustinPedersen/4f4d73eee27a3cea7612275a5376ad96 to your computer and use it in GitHub Desktop.
Automated Rig Checks V1.0.0
"""
"""
import pymel.core as pm
from pprint import pprint
def line_list(in_list):
""" Convert a list into formatted lines for error output. """
str_list = [x.name() if isinstance(x, pm.PyNode) else x for x in in_list]
return '\n\t {}'.format('\n\t '.join(str_list))
def get_namespaces():
""" Returns a list of namespaces present in the scene. """
result = list(set([node.namespace() for node in pm.ls() if node.namespace()]))
if result:
return False, f'The following namespaces are present in the scene: {line_list(result)}'
return True, 'All namespaces removed from scene'
def get_ctl_transforms(round_precision=4):
"""
Finds all transforms in the scene that have '_ctl' in their name and have non-default
values on their translate, rotate, or scale attributes.
:param round_precision: decimal places to round values to.
"""
ctl_transforms = []
for node in pm.ls('*_ctl', type='transform'):
for channel, expected_value in zip(['translate', 'rotate', 'scale'], [0.0, 0.0, 1.0]):
for axis in ['X', 'Y', 'Z']:
attr = f'{channel}{axis}'
if node.attr(attr).isKeyable():
if round(node.attr(attr).get(), round_precision) != expected_value:
ctl_transforms.append(node)
break
if ctl_transforms:
return False, f'The following controls have values on them: {line_list(ctl_transforms)}'
return True, 'All controls are clean.'
def get_visible_meshes_without_skin():
""" Returns a list of every visible mesh in the scene that does not have a skin cluster attached to it. """
visible_meshes_without_skin = []
for mesh in pm.ls(type='mesh', visible=True):
if not mesh.isIntermediate():
if not mesh.listHistory(type='skinCluster'):
visible_meshes_without_skin.append(mesh.getParent())
if visible_meshes_without_skin:
return False, f"The following meshes are visible and aren't skinned: {line_list(visible_meshes_without_skin)}"
return True, 'All meshes that are visible are skinned.'
def get_nodes_with_pasted_name():
""" Returns a list of all nodes in the scene with 'pasted__' in their name. """
nodes_with_pasted_names = pm.ls('*pasted__*')
if nodes_with_pasted_names:
return False, f'The following nodes have been pasted into the scene: {line_list(nodes_with_pasted_names)}'
return True, 'There are no pasted nodes in the scene'
def check_unused_influences():
"""
Checks every skin cluster in the scene and returns a list of joints that
have zero influence on the skin cluster.
"""
unused_influences = list()
for skin_cluster in pm.ls(type='skinCluster'):
skin_geo = skin_cluster.getGeometry()[0]
weight_lookup = dict(zip(skin_cluster.getInfluence(),
zip(*skin_cluster.getWeights(skin_geo))))
for influence, weight_list in weight_lookup.items():
if not all(weight_list):
unused_influences.append(skin_geo.getParent())
break
result = list(set(unused_influences))
if result:
return False, f'The following meshes have unused skinning influences: {line_list(result)}'
return True, 'All skin clusters have been optimized.'
def list_non_deformer_history():
""" Lists the non-deformer history on each mesh in the scene that has a skin cluster. """
result = []
ignore_list = [pm.nt.Joint, pm.nt.Mesh, pm.nt.SkinCluster, pm.nt.BlendShape, pm.nt.NonLinear, pm.nt.DeformBend,
pm.nt.DeformFlare, pm.nt.DeformSine, pm.nt.DeformSquash, pm.nt.DeformTwist, pm.nt.DeformWave,
pm.nt.Transform]
for skin_cluster in pm.ls(type='skinCluster'):
mesh = skin_cluster.getGeometry()[0]
history = pm.listHistory(mesh, interestLevel=2) or []
result.extend([mesh.getParent() for h in history if type(h) not in ignore_list])
result = list(set(result))
if result:
return False, f'The following meshes have non deformer history on them {line_list(result)}'
else:
return True, 'All skinned meshes have only deformer history.'
def check_skinned_mesh_display_layers():
""" Checks if every skinned mesh in the scene belongs to a display layer. """
skinned_meshes = [x.getGeometry()[0].getParent() for x in pm.ls(type='skinCluster')]
skinned_meshes_without_display_layers = []
for mesh in skinned_meshes:
if not pm.listConnections(mesh, type='displayLayer'):
skinned_meshes_without_display_layers.append(mesh)
if skinned_meshes_without_display_layers:
return False, f"The following skinned meshes do not belong to any display layer: " \
f"{line_list(skinned_meshes_without_display_layers)}"
else:
return True, "All skinned meshes belong to a display layer."
def check_extra_cameras():
""" Check the scene for extra cameras """
extra_cameras = []
for cam in pm.ls(type='camera'):
if cam.name() not in ['frontShape', 'perspShape', 'sideShape', 'topShape']:
extra_cameras.append(cam.getParent())
if extra_cameras:
return False, f'There are additional cameras in the scene that need to be removed: {line_list(extra_cameras)}'
return True, 'There are no additional cameras in the scene.'
def check_lights():
""" Check the scene for lights that shouldn't be there """
lights = pm.ls(type='light')
if lights:
return False, f'There are lights in the scene that need to be removed: {line_list(lights)}'
return True, 'There are no lights in the scene.'
def check_image_planes():
""" Check the scene for image planes that shouldn't be there """
image_panes = pm.ls(type='imagePlane')
if image_panes:
return False, f'There are Image Planes in the scene that need to be removed: {line_list(image_panes)}'
return True, 'There are no Image Planes in the scene.'
def check_keyframes():
""" Check that there are no keyframes in the scene """
node_types = ['animCurveTA', 'animCurveTL', 'animCurveTU']
keyed_controls = []
for node_type in node_types:
for keyframe_nodes in pm.ls(type=node_type):
keyed_controls.extend(pm.listConnections(keyframe_nodes.output, destination=True))
result = list(set(keyed_controls))
if result:
return False, f'There are keyframes on the following nodes: {line_list(result)}'
else:
return True, 'There are no keyframes in the scene.'
def check_guide_in_scene():
""" Check that there is no mGear guide in the scene """
guides = pm.ls('guide')
if guides:
return False, f'There are guides in the scene that need to be deleted: {line_list(guides)}'
else:
return True, 'There are no guides in the scene.'
def check_ng_skin_nodes():
""" Check that all the NG skin tools nodes have been removed from the scene """
ng_nodes = pm.ls(type='ngst2SkinLayerData')
if ng_nodes:
return False, f'There are NG Skin Nodes in the scene that need to be deleted: {line_list(ng_nodes)}'
else:
return True, 'There are no NG SKin Nodes in the scene.'
def run_checks(checks):
"""
Run all the given checks and print the feedback from each method.
"""
check_results = []
passed_checks = 0
for check in checks:
status, message = check()
check_results.append(f'PASS: {message}\n' if status else f'FAIL: {message}\n')
if status:
passed_checks += 1
# Print a header
print('=' * 50)
print(f'CHECKS PASSED: {passed_checks}/{len(checks)}')
print('=' * 50 + '\n')
# Print out the results
[print(cr) for cr in check_results]
if __name__ == '__main__':
check_methods = [
get_namespaces,
get_ctl_transforms,
get_visible_meshes_without_skin,
get_nodes_with_pasted_name,
check_unused_influences,
list_non_deformer_history,
check_skinned_mesh_display_layers,
check_extra_cameras,
check_lights,
check_image_planes,
check_keyframes,
check_guide_in_scene,
check_ng_skin_nodes
]
run_checks(check_methods)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment