Monday, 9 August 2021

Useful Scripts: Copy Override Colour

Here's a script I use eeeeeevery day. Turning on an object's override colour is a pain. You have to go into the node editor, open a bunch of nested dropdown menus, use an annoying slider that somehow corresponds to a list of colours in no particular order, and the attribute editor doesn't let you do this for multiple objects at once, so you have to do this for each and every object, and maybe you'll do it to the object's transform when you meant to do it to the shapes, and... there's got to be a better way!

Say you have a bunch of objects, you want them all to have the same override colour. One of them already has the correct colour, but rather than do all that stuff again for each object, simply select the already coloured object, shift select the remaining objects, then run this script!


I get a kick out of building more and more contingencies into a simple script so it can handle any curve ball you throw at it (that's why the script is longer than you might expect for such a simple result.)
This script will work whether the override colour in question is from Maya's override colour index list, or if it's a custom RGB colour. If the first object you selected is coloured but that colour is being inherited from higher in the hierarchy, the script will climb the hierarchy until it identified the source of the colour!
I've tried to comment everything so the script is easy to reverse engineer.
This honestly saves me so much time what with all the time I spend making and colouring control curves. 
 

import maya.cmds as cmds

# Get shape function --------------------------------------------------------------------------------------------
def getShape(node, fullPath=False):
    '''
    Gets and returns the shape node(s) from the passed node.
    If passed a shape node or a joint node, simply returns them unaltered.
    :param node: The node whose shapes you want.
    :param fullPath: Whether to get full path name of shapes.
    '''
    shapesList = []
    # Check if passed node is a transform node
    if cmds.nodeType(node) == 'transform':
        # Get shapes of passed node.
        shapes = cmds.listRelatives(node, shapes=True, path=True, fullPath=fullPath)
        # Test, ARE there any shapes? If not, return None
        if shapes:
            print("Found shapes.")
        else:
            print("No shapes found.")
            return(None)
        # Add any found shapes to shapes list
        for shape in shapes:
            shapesList.append(shapes)
        # Output found shapes
        return(shapesList)
    # If passed node is not a transform node, test if it's some kind of shape node, and simply (and cheekily) pass it back
    elif cmds.nodeType(node) in ['mesh', 'nurbsCurve', 'nurbsSurface']:
        return(node)
    # If passed node is a joint (and thus has no shape node) pass it back
    elif cmds.nodeType(node) == 'joint':
        print("Provided object is a joint. proceding.")
        return(node)
    return(None)

# Get colour from object -----------------------------------------------------------------------------------------------
def getColor(obj):
    '''
    Takes an object and gets gets returns its override colour information.
    If provided object has no override colour info, script climbs hierarchy until it finds the source of the override colour.
    :param obj: object to get override colour from.
    '''
    # Function to extract override colour data from passed object
    def extractColor(obj):
        # Determine if override colour is index mode or RGB mode
        colorMethod = cmds.getAttr(obj + '.overrideRGBColors')
        if colorMethod == False:
            color = cmds.getAttr(obj + '.overrideColor')
        if colorMethod == True:
            color = cmds.getAttr(obj + '.overrideColorRGB')
            color = list(color[0])
        return(color)

    # Function to check if object has any override colour data
    def checkIfColored(obj):
        if cmds.getAttr(obj+'.overrideEnabled') == True:
            value = True
        else:
            value = False
        return(value)

    # Find object with override colour data...
    # ...Start with shape node. If passed object isn't a shape node, look for shapes nodes in its children
    if cmds.nodeType(obj) in ['mesh', 'nurbsCurve', 'nurbsSurface']:
        shape = obj
    else:
        print("Passed coloured object is not a shape node. Checking for shape nodes in its children.")
        shape = getShape(obj)
        if type(shape) == list:
            shape = shape[0]
        if type(shape) == list:
            shape = shape[0]

    # If shape node has no override colour data, check its parent, and grandparent, and so on until script find where the colour data is coming from
    counter = 0
    # Number of parents to examine before giving up the search
    iterations = 300
    startingShape = shape
    while counter < iterations:
        if checkIfColored(shape) == True:
            coloredObj = shape
            if shape != startingShape:
                print("Found source of override colour information: '{}'.".format(shape))
            break
        else:
            if counter == 0:
                print("Provided coloured object has no override colour information. Looking up hierarchy for source of inherited colour information.")
            shape = cmds.listRelatives(shape, parent=1)
            if shape == None:
                cmds.error("Object '{}' has no overrideColor data, nor is it inheriting any from its hierarchy.".format(obj))
            if type(shape) == list:
                shape = shape[0]
            counter += 1
            if counter >= 300:
                print("Reached script limit for hierarchy parents to check without finding any override colour information.")
                break
    # Once found override colour data, extract and return it
    col = extractColor(coloredObj)
    # Figure out whether to use index colour or RGB based on wether the col parameter is one value or three
    if type(col) in [int, float]:
        colorMode = 'index'
        print("Override colour mode: '{}'".format('index'))
    elif type(col) == list and len(col) == 3:
        colorMode = 'RGB'
        print("Override colour mode: '{}'".format('RGB'))
    print("Override colour: '{}'".format(col))
    return(col)

# Assign colour to other objects ----------------------------------------------------------------------------------
def setColor(obj, col):
    '''
    Assigns a given colour to a given object's shape node(s). If the given colour consists of a single value, script
    will assume to be using it as a colour index value. If the given colour consists of a 3 value list, script
    will assume to be using it as HSV values for a custom colour.
    :param obj: The object whose shape nodes are to be recoloured.
    :param col: The colour to be assigned.
    '''
    # Figure out whether to use index colour or RGB based on wether the col parameter is one value or three
    if type(col) in [int, float]:
        colorMode = 'index'
    elif type(col) == list and len(col) == 3:
        colorMode = 'RGB'

    # Colour assign function
    def applyColor(shape=''):
        cmds.setAttr(shape + '.overrideEnabled', True)
        if colorMode == 'index':
            cmds.setAttr(shape + '.overrideRGBColors', 0)
            cmds.setAttr(shape + '.overrideColor', col)
        elif colorMode == 'RGB':
            cmds.setAttr(shape + '.overrideRGBColors', 1)
            cmds.setAttr(shape + '.overrideColorRGB', col[0], col[1], col[2])

    # Perform colour assign function on all shapes found under obj. If no shapes found, assume obj to be joint and assign colour directly to joint.
    applyToShapes = True
    objShapes = cmds.listRelatives(obj, shapes=True)
    if objShapes == None:
        applyToShapes = False
    if applyToShapes == True:
        if type(objShapes) == list:
            if len(objShapes) > 1:
                for i in range(0, len(objShapes)):
                    shape = objShapes[i]
                    applyColor(shape=shape)
            else:
                shape = objShapes[0]
                applyColor(shape=shape)
        else:
            try:
                shape = objShapes[0]
            except:
                shape = objShapes
            applyColor(shape=shape)
    else:
        applyColor(obj)


# Function to do perform all these functions in sequence
def copyOverrideColor():
    '''
    Take the override colour from the first provided object and assign it to all subsequent provided objects.
    '''
    print('-----------------------------------------------------------------------------------------------------')
    # Make list of selected items
    sel = cmds.ls(selection=True)
    # Denote the object to get colour from
    objWithColor = sel[0]
    # Make list of every other selected object
    sel.remove(objWithColor)
    objsToColor = sel
    # Get desired colour from object
    desiredColor = getColor(objWithColor)
    # apply desired colour to shapes
    print("Applying colour to objects.")
    for obj in objsToColor:
        setColor(obj, desiredColor)
    cmds.select(clear=1)
    print('-----------------------------------------------------------------------------------------------------')

copyOverrideColor()

No comments:

Post a Comment