Ben Traje
← Back to maya

Batch Transfer Skin Weights to LODs in Maya using Python

27 Apr 26 (5mo ago)

To save time in weighting different meshes, the workflow is skin a single level of detail (LOD) and transfer the weights other LODs. For games, it is usually the main LODs but for VFX usually the mid or even lores version.

Not limited to different level-of-details (LODS). Any object that needs a rough but quick transfer of weights between objects such as separate objects like props attached to the body.

import maya.cmds as cmds

def batch_transfer_lod_weights(lod_pairs_list):
    print("=== Starting Batch LOD Skin Transfer ===")

    # We will use this dictionary to remember source data so we don't query Maya twice for the same LOD0
    source_cache = {}

    # Loop through each pair in your list
    for pair in lod_pairs_list:
        # Safety check: Ensure there are exactly 2 items in the list
        if len(pair) != 2:
            print(f"Warning: Invalid pair {pair}. Needs exactly 2 items (Source, Target). Skipping.")
            continue

        source_obj = pair[0]
        target_obj = pair[1]

        print(f"\n--- Processing: {source_obj} -> {target_obj} ---")

        # 1. Verify target exists first (fail fast)
        if not cmds.objExists(target_obj):
            print(f"Warning: Target '{target_obj}' does not exist. Skipping.")
            continue

        # 2. Check if we already processed this source
        if source_obj not in source_cache:
            # If it's not in the cache, verify it exists and grab the skin data
            if not cmds.objExists(source_obj):
                print(f"Warning: Source '{source_obj}' does not exist. Skipping.")
                continue

            source_history = cmds.listHistory(source_obj, pruneDagObjects=True) or []
            skin_clusters = cmds.ls(source_history, type="skinCluster")

            if not skin_clusters:
                print(f"Warning: No skin cluster found on '{source_obj}'. Skipping.")
                continue

            temp_skin = skin_clusters[0]
            temp_joints = cmds.skinCluster(temp_skin, query=True, influence=True)

            if not temp_joints:
                print(f"Warning: No bind joints found on skin cluster '{temp_skin}'. Skipping.")
                continue

            # Save this data to our cache so we can reuse it instantly next time
            source_cache[source_obj] = {
                'skin': temp_skin,
                'joints': temp_joints
            }
            print(f"Data for '{source_obj}' cached.")

        # 3. Retrieve the skin and joint data from the cache
        cached_data = source_cache.get(source_obj)
        if not cached_data:
            continue # Skip if caching failed earlier

        source_skin = cached_data['skin']
        bind_joints = cached_data['joints']

        # 4. Check if the target is already skinned. Unbind if necessary.
        target_history = cmds.listHistory(target_obj, pruneDagObjects=True) or []
        if cmds.ls(target_history, type="skinCluster"):
            print(f"'{target_obj}' is already bound. Unbinding...")
            cmds.skinCluster(target_obj, edit=True, unbind=True)

        # 5. Bind the target object to the exact same joints
        target_skin_nodes = cmds.skinCluster(
            bind_joints,
            target_obj,
            toSelectedBones=True,
            bindMethod=0,
            skinMethod=0,
            normalizeWeights=1
        )

        target_skin = target_skin_nodes[0]

        # 6. Copy the skin weights
        cmds.copySkinWeights(
            sourceSkin=source_skin,
            destinationSkin=target_skin,
            noMirror=True,
            surfaceAssociation='closestPoint',
            influenceAssociation='name'
        )

        print(f"Success: '{target_obj}' complete!")

    print("\n=== Batch LOD Skin Transfer Complete! ===")


# --- CONFIGURATION ---
# Edit this list with your own mesh names.
# Each entry must be a list containing exactly two items: ["Source", "Target"]

lod_data_list = [
    ["Eye_LOD0", "Eye_LOD1"],
    ["Eye_LOD0", "Eye_LOD2"],

    ["Outfit_LOD0", "Outfit_LOD1"],
    ["Outfit_LOD0", "Outfit_LOD2"],

    ["Bell_LOD0", "Bell_LOD1"],
    ["Bell_LOD0", "Bell_LOD2"],

    ["MainBody_LOD0", "MainBody_LOD1"],
    ["MainBody_LOD0", "MainBody_LOD2"]
]

# Execute the script
batch_transfer_lod_weights(lod_data_list)

For a regular weight transfer, you can use the one below:

import maya.cmds as cmds
import maya.mel as mel

def transfer_skin_weights():
    # Store selection
    selection = cmds.ls(selection=True)

    # Check to ensure exactly two objects are selected
    if len(selection) != 2:
        cmds.error("Please select exactly two objects: 1) Driver (skinned), 2) Driven (unskinned).")

    driver = selection[0]
    driven = selection[1]

    # Helper function to find skin cluster using MEL's built-in finder
    def get_skin_cluster(obj):
        skin = mel.eval('findRelatedSkinCluster("{}")'.format(obj))
        return skin if skin else None

    driver_skin = get_skin_cluster(driver)
    driven_skin = get_skin_cluster(driven)

    # Validate skin clusters
    if not driver_skin:
        cmds.error("Driver object '{}' doesn't have a skinCluster.".format(driver))

    if driven_skin:
        cmds.error("Driven object '{}' already has a skinCluster. Unbind it first.".format(driven))

    # Get all joints influencing the driver's skinCluster
    bind_joints = cmds.skinCluster(driver_skin, query=True, influence=True)

    # Bind the driven object to the acquired joints
    # toSelectedBones=True ensures it only uses the exact joints from the driver
    new_skin_nodes = cmds.skinCluster(
        bind_joints,
        driven,
        toSelectedBones=True,
        bindMethod=0,       # Closest distance
        skinMethod=0,       # Classic linear
        normalizeWeights=1  # Interactive
    )

    new_skin = new_skin_nodes[0]

    # Copy skin weights from driver to driven
    cmds.copySkinWeights(
        sourceSkin=driver_skin,
        destinationSkin=new_skin,
        noMirror=True,
        surfaceAssociation='closestPoint',
        influenceAssociation=['name', 'closestJoint', 'oneToOne']
    )

    print("Success: Bound '{}' to driver joints and copied skin weights from '{}'.".format(driven, driver))

# Execute the function
transfer_skin_weights()