Ben Traje
← Back to cinema4d

Batch Export and Import Weights in Cinema4D

29 Apr 26 (1mo ago)

Inspired from maya's mGear feature of export and import weights feature. And actually uses the same formatting except for the skinPack, which is to be implemented.

Normally, you can just get by using a weight manager built-in export and import command but it gets hectic if you have to do it in several objects. Hence you need to script something.

Also in this format, easier to rename objects to skin/weight.

Export Command

# Exports to the directory of the current C4D Document


import c4d
import os
import json
import c4d.gui as gui

def export_weights_to_json():
    doc = c4d.documents.GetActiveDocument()
    
    # 1. Get the current document path
    doc_path = doc.GetDocumentPath()
    if not doc_path:
        gui.MessageDialog("Please save your Cinema 4D document first before exporting weights.")
        return

    # 2. Store the selection
    selected_objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
    if not selected_objects:
        gui.MessageDialog("No objects selected. Please select objects to export weights.")
        return

    # Create the weights folder if it doesn't exist
    weights_dir = os.path.join(doc_path, "weights")
    if not os.path.exists(weights_dir):
        try:
            os.makedirs(weights_dir)
        except Exception as e:
            gui.MessageDialog("Failed to create weights directory:\n{}".format(e))
            return

    success_count = 0
    failed_objects = []

    # 3. Process each selected object
    for obj in selected_objects:
        obj_name = obj.GetName()
        
        # Check if it's a Point Object (Polygon/Spline)
        if not obj.IsInstanceOf(c4d.Opoint):
            failed_objects.append(obj_name + " (Not a point object)")
            continue
            
        pt_count = obj.GetPointCount()
        
        # Look for the Weight Tag
        weight_tag = obj.GetTag(c4d.Tweights)
        if not weight_tag:
            failed_objects.append(obj_name + " (No weight tag)")
            continue

        # Extract weights
        weights_dict = {}
        joint_count = weight_tag.GetJointCount()
        
        for j_idx in range(joint_count):
            joint_obj = weight_tag.GetJoint(j_idx, doc)
            if not joint_obj:
                continue
                
            joint_name = joint_obj.GetName()
            joint_weights = {}
            
            for p_idx in range(pt_count):
                w = weight_tag.GetWeight(j_idx, p_idx)
                # Only store non-zero weights to optimize JSON size, rounded to 4 decimals
                if w > 0.0001: 
                    joint_weights[str(p_idx)] = round(w, 4)
            
            # Only add the joint to the dictionary if it actually influences vertices
            if joint_weights:
                weights_dict[joint_name] = joint_weights

        # Construct the JSON payload matching the requested format
        json_data = {
            "bypassObj": [],
            "objDDic": [
                {
                    "blendWeights": {},
                    "nameSpace": "",
                    "normalizeWeights": 1,
                    "objName": obj_name,
                    "skinClsName": "{}_skinCluster".format(obj_name),
                    "skinDataFormat": "compressed",
                    "skinningMethod": 0,
                    "vertexCount": pt_count,
                    "weights": weights_dict
                }
            ],
            "objs": [obj_name]
        }

        # 4. Save the JSON file
        file_path = os.path.join(weights_dir, "{}.json".format(obj_name))
        try:
            with open(file_path, 'w') as f:
                json.dump(json_data, f, indent=4)
            success_count += 1
        except Exception as e:
            failed_objects.append("{} (Write error: {})".format(obj_name, str(e)))

    # 5. Pop up dialog reporting results
    report = "Export Complete!\n\n"
    report += "Successfully exported: {} object(s)\n".format(success_count)
    report += "Failed/Skipped: {} object(s)\n".format(len(failed_objects))
    
    if failed_objects:
        report += "\nSkipped Objects:\n"
        for fail in failed_objects:
            report += "- {}\n".format(fail)
            
    gui.MessageDialog(report)

if __name__ == '__main__':
    export_weights_to_json()

Import Command

# Not same as the mGear where it uses skinPack. Currently, just a folder which sucks a bit because it uses an old UI dialog box. 
# And you need to select objects rather than just browsing the skinPack file and let it decide which objects to skin. That works in Maya easily since it enforces a somewhat strict naming. In C4D, it is okay to have multiple "eye_geo" in the hierarchy :( 
# The object to be selected needs to have no skin deformer or skin tag attached. 

import c4d
import os
import json

def import_weights_from_json():
    doc = c4d.documents.GetActiveDocument()
    
    # 1. Store the selection
    selected_objects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
    if not selected_objects:
        print("Import Cancelled: No objects selected.")
        return

    # 2. Open a folder dialog box
    folder_path = c4d.storage.LoadDialog(title="Select Weights Folder", flags=c4d.FILESELECT_DIRECTORY)
    if not folder_path:
        print("Import Cancelled: No folder selected.")
        return

    print("\n" + "="*40)
    print("--- Starting Weights Import ---")
    print("="*40)
    
    success_list = []
    skipped_has_tag = []
    skipped_no_file = []
    skipped_missing_joint = []

    # Process each selected object
    for obj in selected_objects:
        obj_name = obj.GetName()
        
        # 3. Check if there is a skin weights attached
        if obj.GetTag(c4d.Tweights):
            skipped_has_tag.append(obj_name)
            continue
            
        # 4. Check if the JSON file corresponds with the name in the directory
        json_file_path = os.path.join(folder_path, "{}.json".format(obj_name))
        if not os.path.exists(json_file_path):
            skipped_no_file.append(obj_name)
            continue
            
        # Read JSON
        try:
            with open(json_file_path, 'r') as f:
                data = json.load(f)
        except Exception as e:
            print("Error reading JSON for {}: {}".format(obj_name, e))
            continue

        # Extract weight dictionary safely based on the previous export structure
        try:
            weights_dict = data["objDDic"][0]["weights"]
        except KeyError:
            print("Error: JSON format for {} is invalid or missing 'weights' data.".format(obj_name))
            continue
            
        # 5 & 6. Loop through joints and verify they exist in the scene
        joints_to_bind = []
        missing_joints = []
        
        for joint_name in weights_dict.keys():
            # SearchObject looks through the entire document hierarchy for a matching name
            joint_obj = doc.SearchObject(joint_name)
            if not joint_obj:
                missing_joints.append(joint_name)
            else:
                joints_to_bind.append((joint_name, joint_obj))
                
        # If any joints are missing, flag it and skip the object
        if missing_joints:
            skipped_missing_joint.append("{} (Missing joints: {})".format(obj_name, ", ".join(missing_joints)))
            continue
            
        # --- All checks passed! Time to bind and apply weights ---
        
        # Create Weight Tag
        weight_tag = c4d.BaseTag(c4d.Tweights)
        obj.InsertTag(weight_tag)
        
        # Optional but recommended: Add a Skin Deformer if one doesn't exist
        skin_deformer = obj.GetDown()
        if not skin_deformer or not skin_deformer.IsInstanceOf(c4d.Oskin):
            skin = c4d.BaseObject(c4d.Oskin)
            skin.InsertUnder(obj)
        
        # Add joints to the tag and apply the weights
        for j_name, j_obj in joints_to_bind:
            # Add joint to the weight tag, which returns its new index in the tag
            j_index = weight_tag.AddJoint(j_obj)
            
            # Retrieve the point weights for this joint from the JSON
            joint_point_weights = weights_dict[j_name]
            
            # Apply each point weight
            for pt_idx_str, weight_val in joint_point_weights.items():
                pt_idx = int(pt_idx_str)
                weight_tag.SetWeight(j_index, pt_idx, float(weight_val))
                
        # Update the weight tag internally
        weight_tag.Message(c4d.MSG_UPDATE)
        success_list.append(obj_name)

    # Print the final results to the console
    print("\n--- Import Summary ---")
    print("Successfully bound & imported: {}".format(len(success_list)))
    for name in success_list:
        print("  + {}".format(name))
        
    print("\nSkipped (Already had Weight Tag): {}".format(len(skipped_has_tag)))
    for name in skipped_has_tag:
        print("  - {}".format(name))
        
    print("\nSkipped (No corresponding JSON found): {}".format(len(skipped_no_file)))
    for name in skipped_no_file:
        print("  - {}".format(name))
        
    print("\nSkipped (Missing Joints in scene): {}".format(len(skipped_missing_joint)))
    for item in skipped_missing_joint:
        print("  - {}".format(item))
        
    print("="*40 + "\n")
    
    # Refresh Cinema 4D UI
    c4d.EventAdd()

if __name__ == '__main__':
    # Clears the console so you get a fresh readout every time you run it
    c4d.CallCommand(13957) 
    import_weights_from_json()