Compare commits
6 Commits
master
...
generate_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86ddef499d | ||
|
|
e2a4a07328 | ||
|
|
d9a9c8a8eb | ||
|
|
71c41f9f0e | ||
|
|
f516ac8f66 | ||
|
|
3a5a957cd2 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
output.txt
|
||||
|
||||
*.pyc
|
||||
Mocaps/*
|
||||
352
BvhToDM.py
Normal file
352
BvhToDM.py
Normal file
@@ -0,0 +1,352 @@
|
||||
import bpy
|
||||
import json
|
||||
from mathutils import Vector
|
||||
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"],
|
||||
"chest": ["head", "right_upper_arm", "left_upper_arm"],
|
||||
"head": [],
|
||||
"right_upper_arm": ["right_lower_arm"],
|
||||
"right_lower_arm": ["right_hand"],
|
||||
"right_hand": [],
|
||||
"left_upper_arm": ["left_lower_arm"],
|
||||
"left_lower_arm": ["left_hand"],
|
||||
"left_hand": [],
|
||||
"right_thigh": ["right_shin"],
|
||||
"right_shin": ["right_foot"],
|
||||
"right_foot": [],
|
||||
"left_thigh": ["left_shin"],
|
||||
"left_shin": ["left_foot"],
|
||||
"left_foot": [],
|
||||
}
|
||||
|
||||
# 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"]
|
||||
|
||||
|
||||
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 find_child(self, bone_name) -> Union[str, None]:
|
||||
result = self.settings.child_dict[bone_name]
|
||||
if len(result) == 1:
|
||||
return result[0]
|
||||
else:
|
||||
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:
|
||||
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(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[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[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"
|
||||
|
||||
def _scale_skeleton(self):
|
||||
"""Old method: do not use.
|
||||
"""
|
||||
# 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(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
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
# 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]]
|
||||
|
||||
@staticmethod
|
||||
def quat_bvh_to_DM_ankles(quat):
|
||||
# transform x -> -z and z -> x
|
||||
return [quat[0], quat[3], -quat[2], quat[1]]
|
||||
|
||||
@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)
|
||||
0
__init__.py
Normal file
0
__init__.py
Normal file
@@ -1,361 +0,0 @@
|
||||
import bpy
|
||||
import json
|
||||
from mathutils import Vector
|
||||
context = bpy.context
|
||||
scene = context.scene
|
||||
|
||||
def generate_DM_skeleton():
|
||||
if bpy.ops.object.mode_set.poll():
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# TODO: by disabling the "inherit_rotation" property of some bones, the "matrix_base" does not take into account the rotation of the parent.
|
||||
# This makes sense from an implementation standpoint, but not for our goal, namely:
|
||||
# We want to disable inherit_rotation in order to get the IK-solver going to correct solutions (hips should rotate).
|
||||
# But we want the calculated quaternion to be in LOCAL joint space, so we want it to be in relation to the parent's rotation.
|
||||
# Proposed solution: re-enable the inherit-rotation option and use another IK solver instead. (itasc)
|
||||
bpy.ops.object.armature_add(enter_editmode=True)
|
||||
obj = bpy.data.objects["Armature"]
|
||||
# obj = bpy.context.object
|
||||
obj.name = "DMS"
|
||||
|
||||
# Rename armature
|
||||
armature = obj.data
|
||||
armature.name = "DeepMimicSkeleton"
|
||||
# Delete first auto-generated bone
|
||||
bpy.ops.armature.select_all(action='SELECT')
|
||||
bpy.ops.armature.delete()
|
||||
|
||||
# Start adding bones
|
||||
root = armature.edit_bones.new("root")
|
||||
root.tail = (0.0, 0.0, 0.001)
|
||||
root.translate(Vector((0.0, 0.0, 0.0)))
|
||||
# Spine, chest and head
|
||||
spine = armature.edit_bones.new("spine")
|
||||
spine.parent = root
|
||||
spine.use_connect = True
|
||||
spine.tail = (0.0, 0.0, 0.236151)
|
||||
spine.translate(Vector((0.0, 0.0, 0.0)))
|
||||
|
||||
chest = armature.edit_bones.new("chest")
|
||||
chest.parent = spine
|
||||
chest.use_connect = True
|
||||
chest.tail = (chest.parent.tail[0], chest.parent.tail[1], chest.parent.tail[2] + 0.223894)
|
||||
|
||||
head = armature.edit_bones.new("head")
|
||||
head.parent = chest
|
||||
head.use_connect = True
|
||||
head.tail = (head.parent.tail[0], head.parent.tail[1], head.parent.tail[2] + 0.24)
|
||||
|
||||
# Right arm
|
||||
right_upper_arm = armature.edit_bones.new("right_upper_arm")
|
||||
right_upper_arm.head = (chest.tail[0]-0.15, chest.tail[1], chest.tail[2])
|
||||
right_upper_arm.tail = (chest.tail[0]-0.15, chest.tail[1], chest.tail[2] - 0.28)
|
||||
right_upper_arm.parent = chest
|
||||
# right_upper_arm.use_inherit_rotation = False
|
||||
|
||||
right_lower_arm = armature.edit_bones.new("right_lower_arm")
|
||||
right_lower_arm.parent = right_upper_arm
|
||||
right_lower_arm.use_connect = True
|
||||
right_lower_arm.tail = (right_lower_arm.parent.tail[0],
|
||||
right_lower_arm.parent.tail[1],
|
||||
right_lower_arm.parent.tail[2] - 0.24)
|
||||
|
||||
right_hand = armature.edit_bones.new("right_hand")
|
||||
right_hand.parent = right_lower_arm
|
||||
right_hand.use_connect = True
|
||||
right_hand.tail = (right_hand.parent.tail[0],
|
||||
right_hand.parent.tail[1],
|
||||
right_hand.parent.tail[2] - 0.1)
|
||||
|
||||
# Left arm
|
||||
left_upper_arm = armature.edit_bones.new("left_upper_arm")
|
||||
left_upper_arm.head = (chest.tail[0]+0.15, chest.tail[1], chest.tail[2])
|
||||
left_upper_arm.tail = (chest.tail[0]+0.15, chest.tail[1], chest.tail[2] - 0.28)
|
||||
left_upper_arm.parent = chest
|
||||
# left_upper_arm.use_inherit_rotation = False
|
||||
|
||||
left_lower_arm = armature.edit_bones.new("left_lower_arm")
|
||||
left_lower_arm.parent = left_upper_arm
|
||||
left_lower_arm.use_connect = True
|
||||
left_lower_arm.tail = (left_lower_arm.parent.tail[0],
|
||||
left_lower_arm.parent.tail[1],
|
||||
left_lower_arm.parent.tail[2] - 0.24)
|
||||
|
||||
left_hand = armature.edit_bones.new("left_hand")
|
||||
left_hand.parent = left_lower_arm
|
||||
left_hand.use_connect = True
|
||||
left_hand.tail = (left_hand.parent.tail[0],
|
||||
left_hand.parent.tail[1],
|
||||
left_hand.parent.tail[2] - 0.1)
|
||||
|
||||
# Right leg
|
||||
right_thigh = armature.edit_bones.new("right_thigh")
|
||||
right_thigh.parent = root
|
||||
right_thigh.head = (-0.084887, 0.0, 0.0)
|
||||
right_thigh.tail = (-0.084887, 0.0, -0.421546)
|
||||
# right_thigh.use_inherit_rotation = False
|
||||
|
||||
right_shin = armature.edit_bones.new("right_shin")
|
||||
right_shin.parent = right_thigh
|
||||
right_shin.use_connect = True
|
||||
right_shin.tail = (right_shin.parent.tail[0], right_shin.parent.tail[1], right_shin.parent.tail[2]-0.39)
|
||||
|
||||
right_foot = armature.edit_bones.new("right_foot")
|
||||
right_foot.parent = right_shin
|
||||
right_foot.use_connect = True
|
||||
right_foot.tail = (right_foot.parent.tail[0], right_foot.parent.tail[1]-0.15, right_foot.parent.tail[2])
|
||||
|
||||
# Left leg
|
||||
left_thigh = armature.edit_bones.new("left_thigh")
|
||||
left_thigh.parent = root
|
||||
left_thigh.head = (0.084887, 0.0, 0.0)
|
||||
left_thigh.tail = (0.084887, 0.0, -0.421546)
|
||||
# left_thigh.use_inherit_rotation = False
|
||||
|
||||
left_shin = armature.edit_bones.new("left_shin")
|
||||
left_shin.parent = left_thigh
|
||||
left_shin.use_connect = True
|
||||
left_shin.tail = (left_shin.parent.tail[0], left_shin.parent.tail[1], left_shin.parent.tail[2]-0.39)
|
||||
|
||||
left_foot = armature.edit_bones.new("left_foot")
|
||||
left_foot.parent = left_shin
|
||||
left_foot.use_connect = True
|
||||
left_foot.tail = (left_foot.parent.tail[0], left_foot.parent.tail[1]-0.15, left_foot.parent.tail[2])
|
||||
|
||||
def constrain_DM_skeleton():
|
||||
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['DMS']
|
||||
|
||||
# Root
|
||||
root_con = skel.pose.bones['root'].constraints.new('COPY_TRANSFORMS')
|
||||
root_con.target = bpy.data.objects['rotation_prob']
|
||||
root_con.subtarget = "Hip"
|
||||
|
||||
# Spine
|
||||
spine_con = skel.pose.bones['spine'].constraints.new('COPY_ROTATION')
|
||||
spine_con.target = bpy.data.objects['rotation_prob']
|
||||
spine_con.subtarget = "Hip"
|
||||
|
||||
# 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"
|
||||
|
||||
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 = {
|
||||
# "right_upper_arm": "ShoulderRight",
|
||||
# "right_lower_arm": "ElbowRight",
|
||||
# "left_upper_arm": "ShoulderLeft",
|
||||
# "left_lower_arm": "ElbowLeft",
|
||||
# "right_thigh": "HipRight",
|
||||
# "right_shin": "KneeRight",
|
||||
# "left_thigh": "HipLeft",
|
||||
# "left_shin": "KneeLeft",
|
||||
"right_foot": "AnkleRight",
|
||||
"left_foot": "AnkleLeft",
|
||||
"right_hand": "WristRight",
|
||||
"left_hand": "WristLeft",
|
||||
"chest": "ShoulderCenter",
|
||||
"head": "Head"
|
||||
}
|
||||
|
||||
for bone in bonedict.keys():
|
||||
damped_track(arm, target_arm, bone, bonedict[bone])
|
||||
|
||||
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 = {
|
||||
"right_upper_arm": ("ElbowRight", 1),
|
||||
"right_lower_arm": ("WristRight", 2),
|
||||
"left_upper_arm": ("ElbowLeft", 1),
|
||||
"left_lower_arm": ("WristLeft", 2),
|
||||
"right_thigh": ("KneeRight", 1),
|
||||
"right_shin": ("AnkleRight", 2),
|
||||
"left_thigh": ("KneeLeft", 1),
|
||||
"left_shin": ("AnkleLeft", 2),
|
||||
# "chest": "ShoulderCenter",
|
||||
# "head": "Head"
|
||||
}
|
||||
|
||||
for bone in bonedict.keys():
|
||||
inverse_kinematic_constraint(arm, target_arm, bone, bonedict[bone][0], bonedict[bone][1])
|
||||
|
||||
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='POSE')
|
||||
|
||||
frames = []
|
||||
loopText = "none"
|
||||
|
||||
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()
|
||||
frames.append(generate_frame(arm))
|
||||
|
||||
# Output in dictionary format for easy json dump
|
||||
outputDict = {
|
||||
"Loop": loopText, # "none" or "wrap"
|
||||
"Frames": frames
|
||||
}
|
||||
|
||||
return json.dumps(outputDict, indent=4)
|
||||
|
||||
DMS_name = 'DMS'
|
||||
generate_DM_skeleton()
|
||||
constrain_DM_skeleton()
|
||||
scale_skeleton()
|
||||
|
||||
DMS = bpy.data.objects[DMS_name]
|
||||
target_arm = bpy.data.objects['rotation_prob']
|
||||
follow_mocap_damped_tracks(DMS, target_arm)
|
||||
follow_mocap_IK(DMS, target_arm)
|
||||
|
||||
# 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)
|
||||
44
run.py
Normal file
44
run.py
Normal file
@@ -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()
|
||||
44
run_CMU_MotionBuilder.py
Normal file
44
run_CMU_MotionBuilder.py
Normal file
@@ -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 = "01_06"
|
||||
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("Hips", False, False, False, [0.0, 0.0, 1.0]),
|
||||
"spine": BoneConstructionData("LowerBack", True, False, True, [0.0, 0.0, 1.0]),
|
||||
"chest": BoneConstructionData("Spine", True, False, True, [0.0, 0.0, 1.0], use_DT=True),
|
||||
"head": BoneConstructionData("Spine1", True, False, True, [0.0, 0.0, 1.0], use_DT=True),
|
||||
"right_upper_arm": BoneConstructionData("RightArm", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1),
|
||||
"right_lower_arm": BoneConstructionData("RightForeArm", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2),
|
||||
"right_hand": BoneConstructionData("RightHand", True, False, True, [0.0, 0.0, -1.0], use_DT=True),
|
||||
"left_upper_arm": BoneConstructionData("LeftArm", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1),
|
||||
"left_lower_arm": BoneConstructionData("LeftForeArm", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2),
|
||||
"left_hand": BoneConstructionData("LeftHand", True, False, True, [0.0, 0.0, -1.0], use_DT=True),
|
||||
"right_thigh": BoneConstructionData("RightUpLeg", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1),
|
||||
"right_shin": BoneConstructionData("RightLeg", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2),
|
||||
"right_foot": BoneConstructionData("RightFoot", True, False, True, [0.0, -1.0, 0.0], use_DT=True),
|
||||
"left_thigh": BoneConstructionData("LeftUpLeg", True, True, False, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=1),
|
||||
"left_shin": BoneConstructionData("LeftLeg", True, False, True, [0.0, 0.0, -1.0], use_IK=True, IK_chain_length=2),
|
||||
"left_foot": BoneConstructionData("LeftFoot", True, False, True, [0.0, -1.0, 0.0], use_DT=True),
|
||||
}
|
||||
|
||||
settings = BvhToDMSettings(bonedict, target_name)
|
||||
settings.translation_scale = 0.05
|
||||
settings.output_path = mydir
|
||||
converter = BvhToDM(settings)
|
||||
converter.run()
|
||||
Reference in New Issue
Block a user