How to do a collision retargeting in Lyra Project?

The functionality of copying the pose using an IK Retargeter file is excellent, and I believe it is the future of animation. This solves many problems and, despite the increased processing cost, it makes the work easier for smaller studios.

However, I am having difficulties understanding how the Retarget works for character collision in the Lyra Project. In Boris’s example, the animations on Manny work correctly, even with a skeleton that has more bones in the legs. However, the collision Retarget is not suitable.

I’m using a method to correct the position of the character’s physical assets. It involves creating a version of the SKM_Manny_Invis mesh with the position and rotation of Paragom character’s bones. I can directly import this invisible mesh into the Hero’s PawnCosmeticsComponent, which will fix the positions in Manny’s Physics Assets. If the character’s body is larger, it will be necessary to create a new Physics Asset to correct the scale.
The idea behind this method is to avoid adding extra bones to Manny’s skeleton and focus solely on correcting the position.

To perform this procedure, you need to follow the following steps in Blender and create 3 collections:

SK_Pose Collection: This collection contains the Paragom’s Armatures, and their names should match the ones used in the export. Make sure to set the SK_Pose collection to remain invisible, as its visibility can cause errors.

SK_Base Collection: In this collection, you will find the original SK_Manny. It is important to note that the bones of the SK_Edit Armature will have their parents cleared and will be reparented in each cycle. The SK_Base collection serves as a parameter for this information, meaning that if its hierarchy is modified, it will affect everything else. Just like SK_Pose, the SK_Base collection should also be set to remain invisible, as its visibility can cause errors.

SK_Edit Collection: This collection contains a copy of SK_Manny_Invis. This copy will have its bones repositioned and will be exported. It is important to keep the name of this copy as ‘root’, and it should have the Mesh and Armature visible as they need to be selected.

During the import of the skeletons, make sure to select the ‘ignore leaf bones’ option.

from bpy import data, ops, context
from mathutils import Matrix


class Init():
    
    def __init__(self):
        
        self.ExecuteToOneFileInPose(init=False)
        self.ExecuteToAllFileInPose(init=True)
        pass
    
    def ExecuteToOneFileInPose(self,init=False):
        
        SK_Name = "root_Shinbi"
        Set_Path = "D:\\Documents\\ExportUnrealMesh\\MeshsInvis\\"
        if init:
            Get().GetArmature(Name=SK_Name)
            Execute().CleanParentsBones()
            Execute().RepositionBones()
            Execute().SetParentBones()
            Execute().Export_FBX(Name=SK_Name, Path=Set_Path) 
            pass
        pass
    
    def ExecuteToAllFileInPose(self, init=False):
        global SK_Pose_List, SK_Pose
        
        Set_Path = "D:\\Documents\\ExportUnrealMesh\\MeshsInvis\\"
        if init:
            Get().GetPoseList()
            for SK in SK_Pose_List:
                SK_Pose = SK.pose.bones
                Execute().CleanParentsBones()
                Execute().RepositionBones()
                Execute().SetParentBones()
                Execute().Export_FBX(Name=SK.name, Path=Set_Path)
            pass
        pass
    pass

class Get():
    
    def GetArmature(self, Name = 'ArmatureName'):
        global SK_Pose, SK_Manny, SK_Edit, SK_Edit_Pose
        
        SK_Pose = data.objects[Name].pose.bones
        SK_Manny = data.objects['root_Base'].pose.bones
        SK_Edit = data.objects['root']
        SK_Edit_Pose = data.objects['root'].pose.bones
        pass
    
    def GetPoseList(self):
        global SK_Pose_List, SK_Manny, SK_Edit, SK_Edit_Pose
        
        Coll = data.collections['SK_Pose'].objects
        SK_Pose_List = [obj for obj in Coll if obj.type =="ARMATURE"]
        SK_Manny = data.objects['root_Base'].pose.bones
        SK_Edit = data.objects['root']
        SK_Edit_Pose = data.objects['root'].pose.bones
        pass
    pass


class Execute():
    
    def CleanParentsBones(self):
        global SK_Pose, SK_Manny, SK_Edit, SK_Edit_Pose
        
        context.view_layer.objects.active = SK_Edit
        ops.object.mode_set(mode='OBJECT')
        ops.object.select_all(action='DESELECT')
        SK_Edit.select_set(True)
        ops.object.mode_set(mode='POSE')
        ops.pose.select_all(action='DESELECT')

        for bone_A in SK_Manny:
            if bone_A.name in SK_Pose:
                bone_B = SK_Edit_Pose[bone_A.name]
                bone_B.bone.select = True
                pass
            else:
                bone_B = SK_Edit_Pose[bone_A.name]
                bone_B.bone.select = False
                pass
            pass
        ops.object.mode_set(mode='EDIT')
        ops.armature.parent_clear(type='CLEAR')
        ops.object.mode_set(mode='OBJECT')
        pass
    
    def RepositionBones(self):
        global SK_Pose, SK_Manny, SK_Edit_Pose
        
        ops.object.mode_set(mode='POSE')
        ops.pose.select_all(action='DESELECT')
        
        for bone in SK_Manny:
            if bone.name in SK_Pose:
                bone_edit = SK_Edit_Pose[bone.name]
                bone_edit.matrix = SK_Pose[bone.name].matrix.copy()
                bone_edit.bone.select = True
                pass
            pass
        ops.pose.armature_apply(selected=False)
        pass
    
    def SetParentBones(self):
        global SK_Manny, SK_Edit
        
        ops.object.mode_set(mode='OBJECT')
        ops.object.select_all(action='DESELECT')
        SK_Edit.select_set(True)
        context.view_layer.objects.active = SK_Edit
        ops.object.mode_set(mode='EDIT')
        ops.armature.select_all(action='SELECT')
        ops.armature.parent_clear(type='CLEAR')
        ops.armature.select_all(action='DESELECT')
        
        for bone in SK_Manny:
            parent_bone = SK_Edit.data.edit_bones[bone.name]
            for child in bone.children:
                child_bone = SK_Edit.data.edit_bones[child.name]
                child_bone.parent = parent_bone
                pass
            pass
        ops.object.mode_set(mode='OBJECT')
        pass
    
    def Export_FBX(self, Name='ArmatureName', Path="None" ):

        file = f"{Path}{Name}.fbx"
        ops.object.mode_set(mode='OBJECT')
        ops.object.select_all(action='SELECT')
        ops.export_scene.fbx(filepath=file,
                                check_existing=True,
                                filter_glob='*.fbx',
                                use_selection=True,
                                use_active_collection=False,
                                global_scale=1.0,
                                apply_unit_scale=True,
                                apply_scale_options='FBX_SCALE_NONE',
                                use_space_transform=True,
                                bake_space_transform=False,
                                object_types={'MESH', 'ARMATURE'},
                                use_mesh_modifiers=True,
                                use_mesh_modifiers_render=True,
                                mesh_smooth_type='EDGE',
                                use_subsurf=False,
                                use_mesh_edges=False,
                                use_tspace=False,
                                use_custom_props=True,
                                add_leaf_bones=False,
                                primary_bone_axis='Y',
                                secondary_bone_axis='X',
                                use_armature_deform_only=False,
                                armature_nodetype='NULL',
                                bake_anim=True,
                                bake_anim_use_all_bones=True,
                                bake_anim_use_nla_strips=True,
                                bake_anim_use_all_actions=True,
                                bake_anim_force_startend_keying=True,
                                bake_anim_step=1.0,
                                bake_anim_simplify_factor=1.0,
                                path_mode='AUTO',
                                embed_textures=False,
                                batch_mode='OFF',
                                use_batch_own_dir=True,
                                use_metadata=True,
                                axis_forward='X',
                                axis_up='Z')
        pass
    pass
pass
Init()

If all the settings are correct, the Python script will work properly, and you will achieve a result similar to this.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.