From d9a9c8a8eb589c8da4f5d86a8527f2acf5565646 Mon Sep 17 00:00:00 2001 From: Bart Moyaers Date: Wed, 12 Feb 2020 16:53:00 +0100 Subject: [PATCH] cleanup + move to class --- .gitignore | 2 +- BvhToDM.py | 608 ++++++++++++++++++++++++++-------------------------- __init__.py | 0 run.py | 44 ++++ 4 files changed, 350 insertions(+), 304 deletions(-) create mode 100644 __init__.py create mode 100644 run.py diff --git a/.gitignore b/.gitignore index 2c461fa..86403f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ output.txt - +*.pyc diff --git a/BvhToDM.py b/BvhToDM.py index 43a8c17..1976204 100644 --- a/BvhToDM.py +++ b/BvhToDM.py @@ -1,55 +1,10 @@ import bpy import json from mathutils import Vector -from typing import Union, List -context = bpy.context -scene = context.scene - -# TODO: Based on a dictionary with corresponding bone names (supplied by the user): generate the DeepMimicSkeleton with the exact bone lengths as the example BVH file! -# This should lead to almost perfectly accurate IK solutions! - -target = "rotation_prob" -skel_name = "DeepMimicSkeleton" -# Couple every DM_bone to BVH bone -# Structure: (Target_bone_name: str, inherit_bone_length: bool, inherit_bone_pos: bool, use_connect: bool, bone_direction_vec: list[float]) - -class BoneConstructionData: - def __init__(self, target_name: str, - inherit_bone_length: bool, - inherit_bone_pos: bool, - use_connect: bool, - bone_direction_vec: List[float] = [0.0, 0.0, 1.0], - use_IK: bool = False, - use_DT: bool = False, - IK_chain_length: int = 0): - self.target_name = target_name - self.inherit_bone_length = inherit_bone_length - self.inherit_bone_pos = inherit_bone_pos - self.use_connect = use_connect - self.bone_direction_vec = bone_direction_vec - self.use_IK = use_IK - self.use_DT = use_DT - self.IK_chain_length = IK_chain_length - -bonedict = { - "root": BoneConstructionData("Hip", False, False, False, [0.0, 0.0, 1.0]), - "spine": BoneConstructionData("Hip", False, False, True, [0.0, 0.0, 1.0]), - "chest": BoneConstructionData("ShoulderCenter", False, False, True, [0.0, 0.0, 1.0], use_DT=True), - "head": BoneConstructionData("Head", False, False, True, [0.0, 0.0, 1.0], use_DT=True), - "right_upper_arm": BoneConstructionData("ShoulderRight", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), - "right_lower_arm": BoneConstructionData("ElbowRight", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), - "right_hand": BoneConstructionData("WristRight", True, False, True, [0.0, 0.0, -1.0], use_DT=True), - "left_upper_arm": BoneConstructionData("ShoulderLeft", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), - "left_lower_arm": BoneConstructionData("ElbowLeft", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), - "left_hand": BoneConstructionData("WristLeft", True, False, True, [0.0, 0.0, -1.0], use_DT=True), - "right_thigh": BoneConstructionData("HipRight", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), - "right_shin": BoneConstructionData("KneeRight", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), - "right_foot": BoneConstructionData("AnkleRight", True, False, True, [0.0, -1.0, 0.0], use_DT=True), - "left_thigh": BoneConstructionData("HipLeft", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), - "left_shin": BoneConstructionData("KneeLeft", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), - "left_foot": BoneConstructionData("AnkleLeft", True, False, True, [0.0, -1.0, 0.0], use_DT=True), -} +from typing import Union, List, Dict +import os +# Define the structure of the DeepMimic skeleton child_dict = { "root": ["spine", "right_thigh", "left_thigh"], "spine": ["chest"], @@ -69,282 +24,329 @@ child_dict = { "left_foot": [], } -def find_parent(bone_name, child_dict) -> Union[str, None]: - for bone in child_dict.keys(): - if bone_name in child_dict[bone]: - return bone - return None +# Joints used in the DM frame definition. +joints = [ + "seconds", "hip", "hip", "chest", "neck", "right hip", "right knee", "right ankle", + "right shoulder", "right elbow", "left hip", "left knee", "left ankle", "left shoulder", + "left elbow" +] +# Order of which the DeepMimicSkeleton bones need to be added to frame data. +bone_names = [ + "chest", "head", "right_thigh", "right_shin", "right_foot", + "right_upper_arm", "right_lower_arm", "left_thigh", "left_shin", "left_foot", "left_upper_arm", + "left_lower_arm" +] +# All 1D joints +joints_1d = ["right_shin", "left_shin", "right_lower_arm", "left_lower_arm"] +# Define ankles in order to do correct quaternion adjustment. +ankles = ["right_foot", "left_foot"] -def find_child(bone_name, child_dict) -> Union[str, None]: - result = child_dict[bone_name] - if len(result) == 1: - return result[0] - else: + +class BoneConstructionData: + def __init__(self, target_name: str, + inherit_bone_length: bool, + inherit_bone_pos: bool, + use_connect: bool, + bone_direction_vec: List[float] = [0.0, 0.0, 1.0], + use_IK: bool = False, + use_DT: bool = False, + IK_chain_length: int = 0): + self.target_name = target_name + self.inherit_bone_length = inherit_bone_length + self.inherit_bone_pos = inherit_bone_pos + self.use_connect = use_connect + self.bone_direction_vec = bone_direction_vec + self.use_IK = use_IK + self.use_DT = use_DT + self.IK_chain_length = IK_chain_length + +class BvhToDMSettings: + def __init__(self, bonedict: Dict[str, BoneConstructionData], target_name: str): + self.bonedict = bonedict + self.child_dict = child_dict + self.target_name = target_name + self.skel_name = "DeepMimicSkeleton" + self.translation_scale = 1.0 + self.output_path = os.path.dirname(os.path.realpath(__file__)) + self.output_file_name = "output.txt" + +class BvhToDM: + def __init__(self, settings: BvhToDMSettings): + self.settings = settings + + @staticmethod + def tail_relative(bone, rel_vec: List[float]): + bone.tail = [bone.head[x] + rel_vec[x] for x in range(3)] + + def find_parent(self, bone_name) -> Union[str, None]: + for bone in self.settings.child_dict.keys(): + if bone_name in self.settings.child_dict[bone]: + return bone return None -def tail_relative(bone, rel_vec: List[float]): - bone.tail = [bone.head[x] + rel_vec[x] for x in range(3)] - -def generate_humanoid_skeleton(target, name, bonedict, childdict): - # First fill up dictionary with edit_bone positions - # (Since it seems to be difficult to read the edit_bones of an armature while editing another one...) - target_bone_head_dict = {} - bpy.data.objects[target].select_set(True) - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='EDIT') - for edit_bone in bpy.context.object.data.edit_bones: - target_bone_head_dict[edit_bone.name] = [x for x in edit_bone.head] - bpy.ops.object.mode_set(mode='OBJECT') - - # Add the armature - bpy.ops.object.armature_add(enter_editmode=True) - obj = bpy.data.objects["Armature"] - obj.name = name - - # Rename armature - armature = obj.data - armature.name = name - # Delete first auto-generated bone - bpy.ops.armature.select_all(action='SELECT') - bpy.ops.armature.delete() - - for bone_name in bonedict.keys(): - construction_data: BoneConstructionData = bonedict[bone_name] - if bone_name == "root": - root = armature.edit_bones.new("root") - root.tail = (0.0, 0.0, 0.001) + def find_child(self, bone_name) -> Union[str, None]: + result = self.settings.child_dict[bone_name] + if len(result) == 1: + return result[0] else: - bone = armature.edit_bones.new(bone_name) - parent = find_parent(bone_name, childdict) - if parent is not None: - bone.parent = armature.edit_bones[parent] - bone.use_connect = construction_data.use_connect - if construction_data.inherit_bone_pos: - bone.head = target_bone_head_dict[construction_data.target_name] - if construction_data.inherit_bone_length: - length = bpy.data.objects[target].data.bones[construction_data.target_name].length - tail_relative(bone, [length * x for x in construction_data.bone_direction_vec]) + return None + + def generate_humanoid_skeleton(self): + # First fill up dictionary with edit_bone positions + # (Since it seems to be difficult to read the edit_bones of an armature while editing another one...) + target_bone_head_dict = {} + bpy.data.objects[self.settings.target_name].select_set(True) + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='EDIT') + for edit_bone in bpy.context.object.data.edit_bones: + target_bone_head_dict[edit_bone.name] = [x for x in edit_bone.head] + bpy.ops.object.mode_set(mode='OBJECT') + + # Add the armature + bpy.ops.object.armature_add(enter_editmode=True) + obj = bpy.data.objects["Armature"] + obj.name = self.settings.skel_name + + # Rename armature + armature = obj.data + armature.name = self.settings.skel_name + # Delete first auto-generated bone + bpy.ops.armature.select_all(action='SELECT') + bpy.ops.armature.delete() + + # Cosntruct skeleton + for bone_name in self.settings.bonedict.keys(): + construction_data = self.settings.bonedict[bone_name] + if bone_name == "root": + root = armature.edit_bones.new("root") + root.tail = (0.0, 0.0, 0.001) else: - # TODO: find out some way to guess the correct bone lengths if there are no corresponding bones in the target armature... - if bone_name in ["spine", "chest", "head"]: - tail_relative(bone, [0.0, 0.0, 0.22]) - pass + bone = armature.edit_bones.new(bone_name) + parent = self.find_parent(bone_name) + if parent is not None: + bone.parent = armature.edit_bones[parent] + bone.use_connect = construction_data.use_connect + if construction_data.inherit_bone_pos: + bone.head = target_bone_head_dict[construction_data.target_name] + if construction_data.inherit_bone_length: + length = bpy.data.objects[self.settings.target_name].data.bones[construction_data.target_name].length + self.tail_relative(bone, [length * x for x in construction_data.bone_direction_vec]) + else: + # TODO: find out some way to guess the correct bone lengths if there are no corresponding bones in the target armature... + if bone_name in ["spine", "chest", "head"]: + self.tail_relative(bone, [0.0, 0.0, 0.22]) + pass -def constrain_DM_skeleton(bonedict): - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='POSE') - # Note that pose_bones are under: bpy.data.objects[...].pose.bones - skel = bpy.data.objects[skel_name] + def constrain_DM_skeleton(self): + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='POSE') + # Note that pose_bones are under: bpy.data.objects[...].pose.bones + skel = bpy.data.objects[self.settings.skel_name] - # Root - root_con = skel.pose.bones['root'].constraints.new('COPY_TRANSFORMS') - root_con.target = bpy.data.objects[target] - root_con.subtarget = bonedict['root'].target_name + # Root + root_con = skel.pose.bones['root'].constraints.new('COPY_TRANSFORMS') + root_con.target = bpy.data.objects[self.settings.target_name] + root_con.subtarget = self.settings.bonedict['root'].target_name - # Spine - spine_con = skel.pose.bones['spine'].constraints.new('COPY_ROTATION') - spine_con.target = bpy.data.objects[target] - spine_con.subtarget = bonedict['spine'].target_name + # Spine + spine_con = skel.pose.bones['spine'].constraints.new('COPY_ROTATION') + spine_con.target = bpy.data.objects[self.settings.target_name] + spine_con.subtarget = self.settings.bonedict['spine'].target_name + + # Fixing rotation in elbow and knee joints: + # 1) limit rotation constraint + # 2) y- and z-rotations constrain from 0 to 0 degrees. "Fixed" + # 3) Elbows: limit rotation in "Local space" from -180 to 0 degrees. + # Knees: from 0 to 180 degrees + + # Knees + rightshin = skel.pose.bones['right_shin'] + rightshin.ik_max_x = 3.1415 + rightshin.ik_min_x = 0 + rightshin.use_ik_limit_x = True + rightshin.lock_ik_y = True + rightshin.lock_ik_z = True + rightshin.rotation_mode = 'AXIS_ANGLE' + + leftshin = skel.pose.bones['left_shin'] + leftshin.ik_max_x = 3.1415 + leftshin.ik_min_x = 0 + leftshin.use_ik_limit_x = True + leftshin.lock_ik_y = True + leftshin.lock_ik_z = True + leftshin.rotation_mode = 'AXIS_ANGLE' + + # Elbows + right_lower_arm = skel.pose.bones['right_lower_arm'] + right_lower_arm.ik_max_x = 0 + right_lower_arm.ik_min_x = -3.1415 + right_lower_arm.use_ik_limit_x = True + right_lower_arm.lock_ik_y = True + right_lower_arm.lock_ik_z = True + right_lower_arm.rotation_mode = 'AXIS_ANGLE' + + left_lower_arm = skel.pose.bones['left_lower_arm'] + left_lower_arm.ik_max_x = 0 + left_lower_arm.ik_min_x = -3.1415 + left_lower_arm.use_ik_limit_x = True + left_lower_arm.lock_ik_y = True + left_lower_arm.lock_ik_z = True + left_lower_arm.rotation_mode = 'AXIS_ANGLE' + + # IK settings + skel.pose.ik_solver = "ITASC" + skel.pose.ik_param.mode = "SIMULATION" - # Fixing rotation in elbow and knee joints: - # 1) limit rotation constraint - # 2) y- and z-rotations constrain from 0 to 0 degrees. "Fixed" - # 3) Elbows: limit rotation in "Local space" from -180 to 0 degrees. - # Knees: from 0 to 180 degrees + def _scale_skeleton(self): + """Old method: do not use. + """ + # Scale so that the distance from ankle to shoulders is the same - # Knees - rightshin = skel.pose.bones['right_shin'] - rightshin.ik_max_x = 3.1415 - rightshin.ik_min_x = 0 - rightshin.use_ik_limit_x = True - rightshin.lock_ik_y = True - rightshin.lock_ik_z = True - rightshin.rotation_mode = 'AXIS_ANGLE' + # Set both objects to edit mode + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='EDIT') - leftshin = skel.pose.bones['left_shin'] - leftshin.ik_max_x = 3.1415 - leftshin.ik_min_x = 0 - leftshin.use_ik_limit_x = True - leftshin.lock_ik_y = True - leftshin.lock_ik_z = True - leftshin.rotation_mode = 'AXIS_ANGLE' + shoulder_DM = "right_upper_arm" + shoulder_mocap = "ShoulderRight" + ankle_DM = "right_foot" + ankle_mocap = "AnkleRight" - # Elbows - right_lower_arm = skel.pose.bones['right_lower_arm'] - right_lower_arm.ik_max_x = 0 - right_lower_arm.ik_min_x = -3.1415 - right_lower_arm.use_ik_limit_x = True - right_lower_arm.lock_ik_y = True - right_lower_arm.lock_ik_z = True - right_lower_arm.rotation_mode = 'AXIS_ANGLE' + DMS = bpy.data.armatures['DeepMimicSkeleton'] + mocap_skel = bpy.data.armatures['rotation_prob'] - left_lower_arm = skel.pose.bones['left_lower_arm'] - left_lower_arm.ik_max_x = 0 - left_lower_arm.ik_min_x = -3.1415 - left_lower_arm.use_ik_limit_x = True - left_lower_arm.lock_ik_y = True - left_lower_arm.lock_ik_z = True - left_lower_arm.rotation_mode = 'AXIS_ANGLE' + # Only take into account z-component + mocap_height = mocap_skel.bones[shoulder_mocap].head_local[2] - mocap_skel.bones[ankle_mocap].head_local[2] + DM_height = DMS.bones[shoulder_DM].head_local[2] - DMS.bones[ankle_DM].head_local[2] + + scale_factor = mocap_height/DM_height - # IK settings - skel.pose.ik_solver = "ITASC" - skel.pose.ik_param.mode = "SIMULATION" - -def scale_skeleton(): - # Scale so that the distance from ankle to shoulders is the same - - # Set both objects to edit mode - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='EDIT') - - shoulder_DM = "right_upper_arm" - shoulder_mocap = "ShoulderRight" - ankle_DM = "right_foot" - ankle_mocap = "AnkleRight" - - DMS = bpy.data.armatures['DeepMimicSkeleton'] - mocap_skel = bpy.data.armatures['rotation_prob'] - - # Only take into account z-component - mocap_height = mocap_skel.bones[shoulder_mocap].head_local[2] - mocap_skel.bones[ankle_mocap].head_local[2] - DM_height = DMS.bones[shoulder_DM].head_local[2] - DMS.bones[ankle_DM].head_local[2] - - scale_factor = mocap_height/DM_height - - # Select all bones - bpy.data.objects['DMS'].select_set(True) - bpy.ops.armature.select_all(action='SELECT') - - # Scale - bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) - -def damped_track(arm, target_arm, bone: str, target_bone: str): - constraint = arm.pose.bones[bone].constraints.new('DAMPED_TRACK') - constraint.target = target_arm - constraint.subtarget = target_bone - constraint.head_tail = 1.0 - -def follow_mocap_damped_tracks(arm, target_arm, bonedict): - for bone in bonedict.keys(): - bone_data = bonedict[bone] - if bone_data.use_DT: - damped_track(arm, target_arm, bone, bone_data.target_name) - -def inverse_kinematic_constraint(arm, target_arm, bone: str, target_bone: str, chain_count: int): - constraint = arm.pose.bones[bone].constraints.new('IK') - constraint.target = target_arm - constraint.subtarget = target_bone - constraint.use_tail = True - constraint.use_location = True - constraint.use_stretch = True - constraint.iterations = 500 - constraint.chain_count = chain_count - -def follow_mocap_IK(arm, target_arm, bonedict, child_dict): - for bone in bonedict.keys(): - bone_data = bonedict[bone] - if bone_data.use_IK: - inverse_kinematic_constraint(arm, target_arm, bone, bonedict[find_child(bone, child_dict)].target_name, bone_data.IK_chain_length) - -def quat_bvh_to_DM(quat): - # transform x -> -z and z -> -x, except for ankles and root! - return [quat[0], -quat[3], -quat[2], -quat[1]] - -def quat_bvh_to_DM_ankles(quat): - # transform x -> -z and z -> x - return [quat[0], quat[3], -quat[2], quat[1]] - -def quat_bvh_to_DM_root(quat): - return [quat[0], quat[3], quat[2], -quat[1]] - -def pos_blender_DM(pos, scale): - # y -> z, z -> -y? - return [-scale*pos[1], scale*pos[2], -scale*pos[0]] - -def generate_frame(arm): - - joints = [ - "seconds", "hip", "hip", "chest", "neck", "right hip", "right knee", "right ankle", - "right shoulder", "right elbow", "left hip", "left knee", "left ankle", "left shoulder", - "left elbow" - ] - - bone_names = [ - "chest", "head", "right_thigh", "right_shin", "right_foot", - "right_upper_arm", "right_lower_arm", "left_thigh", "left_shin", "left_foot", "left_upper_arm", - "left_lower_arm" - ] - - joints_1d = ["right_shin", "left_shin", "right_lower_arm", "left_lower_arm"] - ankles = ["right_foot", "left_foot"] - - result = [] - result.append(1.0 / bpy.context.scene.render.fps) - - translation_scale = 1.2 - pos = pos_blender_DM(arm.pose.bones['root'].head, translation_scale) - result.extend(pos) - root_quat = quat_bvh_to_DM_root(arm.pose.bones['root'].rotation_quaternion) - result.extend(root_quat) - - for bone_name in bone_names: - if bone_name in joints_1d: - # invert knee angles (no clue why) - if bone_name in joints_1d[:2]: - angle = -arm.pose.bones[bone_name].rotation_axis_angle[0] - else: - angle = arm.pose.bones[bone_name].rotation_axis_angle[0] - result.append(angle) - else: - # TODO: these rotations are absolute rotations! But we need local rotations... In the local bone frame... - if bone_name in ankles: - quat_DM = quat_bvh_to_DM_ankles(arm.pose.bones[bone_name].rotation_quaternion) - else: - quat_DM = quat_bvh_to_DM(arm.pose.bones[bone_name].rotation_quaternion) - result.extend([x for x in quat_DM]) - - return result - -def generate_frames(arm): - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='EDIT') # Select all bones - bpy.data.objects[skel_name].select_set(True) + bpy.data.objects['DMS'].select_set(True) bpy.ops.armature.select_all(action='SELECT') - if bpy.ops.object.mode_set.poll(): - bpy.ops.object.mode_set(mode='POSE') + # Scale + bpy.ops.transform.resize(value=(scale_factor, scale_factor, scale_factor)) - frames = [] - loopText = "none" - for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1): - # for frame in range(bpy.context.scene.frame_end + 1): - bpy.context.scene.frame_set(frame) - # Apply visual transform to pose - bpy.ops.pose.visual_transform_apply() - # bpy.context.view_layer.update() - frames.append(generate_frame(arm)) + def damped_track(self, bone: str, target_bone: str): + constraint = bpy.data.objects[self.settings.skel_name].pose.bones[bone].constraints.new('DAMPED_TRACK') + constraint.target = bpy.data.objects[self.settings.target_name] + constraint.subtarget = target_bone + constraint.head_tail = 1.0 - # Output in dictionary format for easy json dump - outputDict = { - "Loop": loopText, # "none" or "wrap" - "Frames": frames - } + def follow_mocap_damped_tracks(self): + for bone in self.settings.bonedict.keys(): + bone_data = self.settings.bonedict[bone] + if bone_data.use_DT: + self.damped_track(bone, bone_data.target_name) - return json.dumps(outputDict, indent=4) + def inverse_kinematic_constraint(self, bone: str, target_bone: str, chain_count: int): + constraint = bpy.data.objects[self.settings.skel_name].pose.bones[bone].constraints.new('IK') + constraint.target = bpy.data.objects[self.settings.target_name] + constraint.subtarget = target_bone + constraint.use_tail = True + constraint.use_location = True + constraint.use_stretch = True + constraint.iterations = 500 + constraint.chain_count = chain_count + + def follow_mocap_IK(self): + for bone in self.settings.bonedict.keys(): + bone_data = self.settings.bonedict[bone] + if bone_data.use_IK: + self.inverse_kinematic_constraint( + bone, + self.settings.bonedict[self.find_child(bone)].target_name, + bone_data.IK_chain_length + ) -generate_humanoid_skeleton(target, skel_name, bonedict, child_dict) -constrain_DM_skeleton(bonedict) -# scale_skeleton() + # TODO: find out why these adjustments are necessary. + # Probably has to do with: + # 1) In which direction the bone's rest position is defined. + # 2) The difference in worldframes. (Different axes between Blender and DeepMimic.) + @staticmethod + def quat_bvh_to_DM(quat): + # transform x -> -z and z -> -x, except for ankles and root! + return [quat[0], -quat[3], -quat[2], -quat[1]] -DMS = bpy.data.objects[skel_name] -target_arm = bpy.data.objects[target] -follow_mocap_damped_tracks(DMS, target_arm, bonedict) -follow_mocap_IK(DMS, target_arm, bonedict, child_dict) + @staticmethod + def quat_bvh_to_DM_ankles(quat): + # transform x -> -z and z -> x + return [quat[0], quat[3], -quat[2], quat[1]] -# Save all quaternions in DeepMimic format, frame per frame! -generated_text = generate_frames(DMS) -# print(generated_text) -with open("C:\\UntrackedGit\\BvhToDM_Blender"+ "\\output.txt", "w") as output: - output.write(generated_text) \ No newline at end of file + @staticmethod + def quat_bvh_to_DM_root(quat): + return [quat[0], quat[3], quat[2], -quat[1]] + + @staticmethod + def pos_blender_DM(pos, scale): + # y -> z, z -> -y? + return [-scale*pos[1], scale*pos[2], -scale*pos[0]] + + def generate_frame(self, translation_scale): + arm = bpy.data.objects[self.settings.skel_name] + result = [] + result.append(1.0 / bpy.context.scene.render.fps) + + pos = self.pos_blender_DM(arm.pose.bones['root'].head, translation_scale) + result.extend(pos) + root_quat = self.quat_bvh_to_DM_root(arm.pose.bones['root'].rotation_quaternion) + result.extend(root_quat) + + for bone_name in bone_names: + if bone_name in joints_1d: + # invert knee angles (no clue why) + if bone_name in joints_1d[:2]: + angle = -arm.pose.bones[bone_name].rotation_axis_angle[0] + else: + angle = arm.pose.bones[bone_name].rotation_axis_angle[0] + result.append(angle) + else: + # TODO: these rotations are absolute rotations! But we need local rotations... In the local bone frame... + if bone_name in ankles: + quat_DM = self.quat_bvh_to_DM_ankles(arm.pose.bones[bone_name].rotation_quaternion) + else: + quat_DM = self.quat_bvh_to_DM(arm.pose.bones[bone_name].rotation_quaternion) + result.extend([x for x in quat_DM]) + + return result + + def generate_frames(self, translation_scale): + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='EDIT') + # Select all bones + bpy.data.objects[self.settings.skel_name].select_set(True) + bpy.ops.armature.select_all(action='SELECT') + + if bpy.ops.object.mode_set.poll(): + bpy.ops.object.mode_set(mode='POSE') + + frames = [] + loopText = "none" + for frame in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1): + # for frame in range(bpy.context.scene.frame_end + 1): + bpy.context.scene.frame_set(frame) + # Apply visual transform to pose + bpy.ops.pose.visual_transform_apply() + # bpy.context.view_layer.update() + frames.append(self.generate_frame(translation_scale)) + + # Output in dictionary format for easy json dump + outputDict = { + "Loop": loopText, # "none" or "wrap" + "Frames": frames + } + + return json.dumps(outputDict, indent=4) + + def run(self): + self.generate_humanoid_skeleton() + self.constrain_DM_skeleton() + self.follow_mocap_damped_tracks() + self.follow_mocap_IK() + + # Save all quaternions in DeepMimic format, frame per frame! + generated_text = self.generate_frames(self.settings.translation_scale) + # print(generated_text) + with open(self.settings.output_path + "/" + self.settings.output_file_name, "w") as output: + output.write(generated_text) \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/run.py b/run.py new file mode 100644 index 0000000..307dc6a --- /dev/null +++ b/run.py @@ -0,0 +1,44 @@ +import bpy +import sys +import os + +mydir = "C:\\UntrackedGit\\BvhToDM_Blender" +if not mydir in sys.path: + sys.path.append(mydir) +print(mydir) + +import BvhToDM +import importlib +importlib.reload(BvhToDM) + +from BvhToDM import BoneConstructionData, BvhToDM, BvhToDMSettings + +target_name = "rotation_prob" +skel_name = "DeepMimicSkeleton" + +# Couple every DM_bone to BVH bone +# Structure: (Target_bone_name: str, inherit_bone_length: bool, inherit_bone_pos: bool, use_connect: bool, bone_direction_vec: list[float]) +bonedict = { + "root": BoneConstructionData("Hip", False, False, False, [0.0, 0.0, 1.0]), + "spine": BoneConstructionData("Hip", False, False, True, [0.0, 0.0, 1.0]), + "chest": BoneConstructionData("ShoulderCenter", False, False, True, [0.0, 0.0, 1.0], use_DT=True), + "head": BoneConstructionData("Head", False, False, True, [0.0, 0.0, 1.0], use_DT=True), + "right_upper_arm": BoneConstructionData("ShoulderRight", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), + "right_lower_arm": BoneConstructionData("ElbowRight", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), + "right_hand": BoneConstructionData("WristRight", True, False, True, [0.0, 0.0, -1.0], use_DT=True), + "left_upper_arm": BoneConstructionData("ShoulderLeft", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), + "left_lower_arm": BoneConstructionData("ElbowLeft", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), + "left_hand": BoneConstructionData("WristLeft", True, False, True, [0.0, 0.0, -1.0], use_DT=True), + "right_thigh": BoneConstructionData("HipRight", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), + "right_shin": BoneConstructionData("KneeRight", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), + "right_foot": BoneConstructionData("AnkleRight", True, False, True, [0.0, -1.0, 0.0], use_DT=True), + "left_thigh": BoneConstructionData("HipLeft", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1), + "left_shin": BoneConstructionData("KneeLeft", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2), + "left_foot": BoneConstructionData("AnkleLeft", True, False, True, [0.0, -1.0, 0.0], use_DT=True), +} + +settings = BvhToDMSettings(bonedict, target_name) +settings.translation_scale = 1.2 +settings.output_path = mydir +converter = BvhToDM(settings) +converter.run() \ No newline at end of file