Create Blendshape Target with python

Create Blendshape Target with python

jonsX942W
Enthusiast Enthusiast
8,129 Views
6 Replies
Message 1 of 7

Create Blendshape Target with python

jonsX942W
Enthusiast
Enthusiast

Hi Everyone,  I have tried for a couple days to create a blendshape target correctly and I can't seem to find a way to do it. The help documentation on this is not very clear. I think because I am not making a separate object as a target I am just wanting to use the original shape and edit it just like you do when you hit the "add target" button in the Shape Editor. I think maya is doing some very tricky stuff. 

I was mostly trying to use this option but it never made the connections correctly.

BlendFix_01 is the name of my primary blendshape


mc.blendShape( "pSphereShape2", edit=True, t=("pSphereShape2", 1, "pSphere2", 1) )


I have found this to work with multiple objects but can't seem to do it without creating multiple objects. Surly there is a much simpler solution? Also I understand what most of this code is doing except the last line. This I don't understand. I am not a newbie to Maya but I am pretty new to scripting. 

any help and I would soooo Grateful!

#remove an existing blend
blendNode = 'BlendFix_01'
base = 'pSphere2'
target = 'pSphere3'
index = 1
maxValue = 1.0
mc.blendShape( blendNode, edit=True, rm=True, t=[(base, index, target, maxValue),(base, index, target, maxValue)] )

#add blend (must be an empty slot, if its used, you have to remove it first)
target = 'pSphere1'
index = 2

mc.blendShape( blendNode, edit=True, t=(base, index, target, maxValue) )

#replace blend via connection
blendNode = 'BlendFix_01'
meshIn = 'pSphereShape4'
geoIndex = 0
index = 1
mc.connectAttr( '%s.pSphere1[0]' % meshIn,
'%s.inputTarget[%s].inputTargetGroup[%s].inputTargetItem[6000].inputGeomTarget' % (blendNode, geoIndex, index), f=1 )

 

 

0 Likes
Accepted solutions (1)
8,130 Views
6 Replies
Replies (6)
Message 2 of 7

jmreinhart
Advisor
Advisor
Accepted solution

So the a blendShape target can be an actual mesh or it can be a set of deltas (the offset between your original shape and the target mesh). It sounds like you don't want the target to be an actual mesh.

 

To do that all you need to do is create the blendShape the way you were before and then delete the target mesh. The target will still exist on the blendShape node, but only the deltas will be recorded.

 

To change those deltas by sculpting on the original mesh you can press edit in the shape editor or you can use these two commands which do the same thing.

 

#turns on the edit mode for the target with given target index
cmds.sculptTarget('blendShape1', e = True, target = target_index)

#turns off edit mode
cmds.sculptTarget('blendShape1', e = True, target = -1)

 

These commands need to use the target index which is a bit annoying so you can use this function to get the target index if you know the target name. 

 

#get the target index given the target name
def get_blendShape_target_index(blendShape_node,blendShape_target):
    '''
    Gets the index of the given target on the given blendShape node.
    
    blendShape_node = The blendShape node the target is on.
    blendShape_target = The target you want to get the index for.
    
    Returns the target index as an integer.
    '''
    #run error checks
    if not cmds.objExists(blendShape_node):
        cmds.error('"{}" does not exist'.format(blendShape_node))
    
    if cmds.nodeType(blendShape_node) != 'blendShape':
        cmds.error('"{}" is not a blendShape node'.format(blendShape_node))

    #create a dictionary of targets and indices
    target_attr_list = cmds.aliasAttr(blendShape_node, query=True)
    target_list =  target_attr_list[::2]
    attr_list =  target_attr_list[1::2]
    target_index_list = [int(attr.split('[')[-1][:-1]) for attr in attr_list]    
    target_index_dict = dict(zip(target_list,target_index_list))
    
    #get the blendShape index 
    if blendShape_target in target_list:
        target_index = target_index_dict[blendShape_target]
    else:
        cmds.error('"{}" has no target "{}"'.format(blendShape_node,blendShape_target))
        
    return target_index

 

Hopefully that helps you, but if there's still something you don't understand let me know.

0 Likes
Message 3 of 7

jonsX942W
Enthusiast
Enthusiast

Ok, Thank you soooooo much! Where is your tip jar :). I never understood that whole deltas thing. So by creating it then Deleting it and knowing that the deltas are saved was very helpful! I'd buy you lunch if i could.  Thanks. I also realized that creating things in the Blenshape Window does not help when trying to see what maya is doing in the script editor. Its much better to go through the top down menus. 

Message 4 of 7

Anonymous
Not applicable
Hello, were you able to find a way to create targets through python without duplicating objects ?
0 Likes
Message 5 of 7

jmreinhart
Advisor
Advisor
def add_empty_blendShape_target(blendShape_node,target_name = 'emptyTarget',tangent_space = False):
    '''
    Adds a new target to the given blendshape node. The new target will have no deltas.
    
    blendShape_node = The blendShape node that contains the target you want to extract.
    target_name = The name you want to be assigned to the new target.
    
    Returns the name of the new target (which may change to avoid conflicting names) and the index of the new target.
    '''
    #run error checks
    if not cmds.objExists(blendShape_node):
        cmds.error('"{}" does not exist')
    
    if cmds.nodeType(blendShape_node) != 'blendShape':
        cmds.error('"{}" is not a blendShape node')
        
    #get the list of target index plugs
    mSelectionList = om2.MSelectionList()
    mSelectionList.add(blendShape_node)
    mObject_blendShape_node = mSelectionList.getDependNode(0)
    mFnDependencyNode = om2.MFnDependencyNode(mObject_blendShape_node)
    target_mPlug = mFnDependencyNode.findPlug("inputTarget",False).elementByLogicalIndex(0).child(0)
    used_index_list = list(target_mPlug.getExistingArrayAttributeIndices())
    if used_index_list == []:
        max_index = -1
    else:
        max_index = max(used_index_list)
    
    #evaluate the next index in the list
    target_mPlug.elementByLogicalIndex(max_index + 1).asMObject()
    target_mPlug.evaluateNumElements()
    
    input_target_item_mPlug= target_mPlug.elementByLogicalIndex(max_index + 1).child(0)
    input_target_item_mPlug.elementByLogicalIndex(6000)
    input_target_item_mPlug.evaluateNumElements()

    weight_mPlug = mFnDependencyNode.findPlug("weight",False)
    weight_mPlug.elementByLogicalIndex(max_index + 1).asFloat()
    weight_mPlug.evaluateNumElements()
    
    #set the target to use tangent space#!!! very hacky atm and it's dependent on a blendShape node exisitng
    if tangent_space:
        base_obj = cmds.blendShape(blendShape_node, q = True, g = True)
        if not base_obj:
            cmds.error('"{}" does not affect any geometry and therefore there is no tangent space'.format(blendShape_node))
        temp_obj = cmds.duplicate(base_obj[0])[0]
        cmds.blendShape(blendShape_node, e = True, target = [base_obj[0], max_index + 1, temp_obj, 1.0], tangentSpace = True) 
        cmds.delete(temp_obj)
    
    #assign the name to the new target
    if not cmds.attributeQuery(target_name, node = blendShape_node, exists = True):
        cmds.aliasAttr(target_name,'{}.w[{}]'.format(blendShape_node,max_index + 1))
        return target_name, max_index + 1

    else:
        ending_enumeration = re.findall("\d+$", target_name)
        if ending_enumeration:
            numberless_target = target_name[: - len(ending_enumeration[0])]
            ending_enumeration = int(ending_enumeration[0])
        else:
            ending_enumeration = 1
            numberless_target = target_name
            
        while cmds.attributeQuery('{}{}'.format(numberless_target,ending_enumeration), node = blendShape_node, exists = True):
            ending_enumeration +=1
            
        cmds.aliasAttr(numberless_target + str(ending_enumeration),'{}.w[{}]'.format(blendShape_node,max_index + 1))
        return numberless_target + str(ending_enumeration), max_index + 1

This is a snippet of code from a set of utils for modifying weight and deltas.  I haven't Revisited this particular function in a long time  so it's possible that there is a cleaner way but this function has worked reliably for me for a long time.



Message 6 of 7

jonsX942W
Enthusiast
Enthusiast

Thanks but what are you importing to use. 

om2.

 Also is the initial selection that goes in the function a object or a blendshape? (I am assuming object)

Message 7 of 7

jmreinhart
Advisor
Advisor

import maya.api.OpenMaya as om2. The inputs for the function are explained at the top of the function, it is not selection based.

0 Likes