En3D-human-animation / skinning.py
dylanebert's picture
dylanebert HF staff
add files
5e29a61
import bpy
import os
import sys
import argparse
from mathutils import Matrix
class ArgumentParserForBlender(argparse.ArgumentParser):
"""
This class is identical to its superclass, except for the parse_args
method (see docstring). It resolves the ambiguity generated when calling
Blender from the CLI with a python script, and both Blender and the script
have arguments. E.g., the following call will make Blender crash because
it will try to process the script's -a and -b flags:
>>> blender --python my_script.py -a 1 -b 2
To bypass this issue this class uses the fact that Blender will ignore all
arguments given after a double-dash ('--'). The approach is that all
arguments before '--' go to Blender, arguments after go to the script.
The following calls work fine:
>>> blender --python my_script.py -- -a 1 -b 2
>>> blender --python my_script.py --
"""
def _get_argv_after_doubledash(self):
"""
Given the sys.argv as a list of strings, this method returns the
sublist right after the '--' element (if present, otherwise returns
an empty list).
"""
try:
idx = sys.argv.index("--")
return sys.argv[idx + 1:] # the list after '--'
except ValueError as e: # '--' not in the list:
return []
# overrides superclass
def parse_args(self):
"""
This method is expected to behave identically as in the superclass,
except that the sys.argv list will be pre-processed using
_get_argv_after_doubledash before. See the docstring of the class for
usage examples and details.
"""
return super().parse_args(args=self._get_argv_after_doubledash())
def getKeyframes(ob):
if ob.type in ['MESH', 'ARMATURE'] and ob.animation_data:
for fc in ob.animation_data.action.fcurves:
if fc.data_path.endswith('rotation_euler'):
keyframe_list = []
for key in fc.keyframe_points:
# print('frame:',key.co[0],'value:',key.co[1])
keyframe_list.append(key.co[0])
keyframe_list = list(set(keyframe_list))
print('keyframe_list:')
# print(keyframe_list)
print(len(keyframe_list))
firstKFN = int(keyframe_list[0])
lastKFN = int(keyframe_list[-1])
# Only needs to check animation of one bone
return firstKFN, lastKFN
def init_scene():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
def add_material_for_obj(obj, filepath):
obj.data.materials.clear()
# Load image into Blender
mat_name = 'mat' + '_%s' % os.path.basename(filepath)[:-4]
mat = bpy.data.materials.new(name=mat_name)
mat.use_nodes = True
obj.data.materials.append(mat)
matnodes = mat.node_tree.nodes
tex = matnodes.new("ShaderNodeTexImage")
# Assign the loaded image to the diffuse texture node
tex.image = bpy.data.images.load(filepath)
disp = bpy.data.materials[mat_name].node_tree.nodes["Principled BSDF"].inputs['Base Color']
mat.node_tree.links.new(disp, tex.outputs[0])
def import_obj(obj_path, img_path=None):
bpy.ops.import_scene.obj(filepath=obj_path, split_mode="OFF")
bpy.ops.object.shade_smooth()
# For some mysterious raison, this is necessary otherwise I cannot toggle shade smooth / shade flat
mesh = bpy.context.selected_objects[0]
if img_path is not None and os.path.exists(img_path):
add_material_for_obj(mesh, img_path)
else:
print('no texture for %s' % obj_path)
return mesh
def import_skeleton(filepath):
bpy.ops.import_anim.bvh(filepath=filepath, filter_glob="*.bvh", target='ARMATURE', global_scale=1, frame_start=1,
use_fps_scale=False, use_cyclic=False, rotate_mode='NATIVE', axis_forward='-Z', axis_up='Y')
def export_animated_mesh(gltf_path, IsAnimation):
# Create output directory if needed
output_dir = os.path.dirname(os.path.abspath(gltf_path))
if not os.path.isdir(output_dir):
os.makedirs(output_dir, exist_ok=True)
bpy.ops.object.select_all(action='SELECT')
if gltf_path != '':
bpy.ops.export_scene.gltf(filepath=gltf_path, export_format='GLB', export_selected=True,
export_morph=IsAnimation)
def remove_keyframes(object, frame):
action = object.animation_data.action
if action is None:
return
for fc in action.fcurves:
object.keyframe_delete(data_path=fc.data_path, frame=frame)
def apply_transfrom(ob, use_location=False, use_rotation=False, use_scale=False):
mb = ob.matrix_basis
I = Matrix()
loc, rot, scale = mb.decompose()
# rotation
T = Matrix.Translation(loc)
R = mb.to_3x3().normalized().to_4x4()
S = Matrix.Diagonal(scale).to_4x4()
transform = [I, I, I]
basis = [T, R, S]
def swap(i):
transform[i], basis[i] = basis[i], transform[i]
if use_location:
swap(0)
if use_rotation:
swap(1)
if use_scale:
swap(2)
M = transform[0] @ transform[1] @ transform[2]
if hasattr(ob.data, "transform"):
ob.data.transform(M)
for c in ob.children:
c.matrix_local = M @ c.matrix_local
ob.matrix_basis = basis[0] @ basis[1] @ basis[2]
if __name__ == '__main__':
parser = ArgumentParserForBlender()
parser.add_argument('--input', dest='input_dir', type=str, required=True,
help='Input directory')
parser.add_argument('--gltf_path', dest='gltf_path', type=str, required=True,
help='Input directory')
parser.add_argument('--action', dest='action', type=str, required=False,
help='action name')
args = parser.parse_args()
input_dir = args.input_dir
gltf_path = args.gltf_path
action = args.action
init_scene()
obj_path = os.path.join(input_dir, 'body.obj')
img_path = os.path.join(input_dir, 'body.png')
mesh = import_obj(obj_path, img_path=img_path)
skeleton_path = os.path.join(input_dir, 'skeleton_a.bvh')
import_skeleton(skeleton_path)
skeleton = bpy.context.selected_objects[0]
bpy.context.scene.render.fps = 30
## resize mesh, ske
times = 10000
mesh.scale = (times, times, times)
skeleton.scale = (times, times, times)
mesh.select_set(False)
skeleton.select_set(True)
bpy.context.view_layer.objects.active = skeleton
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.transforms_clear()
bpy.ops.object.mode_set(mode='OBJECT')
mesh.select_set(True)
skeleton.select_set(True)
bpy.context.view_layer.objects.active = skeleton
bpy.ops.object.parent_set(type='ARMATURE_AUTO')
ob = bpy.context.active_object
ob.scale = (1, 1, 1)
apply_transfrom(mesh, use_scale=True)
remove_keyframes(bpy.context.object, 1)
firstKFN, lastKFN = getKeyframes(skeleton)
IsAnimation = not (firstKFN == lastKFN)
if IsAnimation:
# Set Frame start and end
bpy.data.scenes[0].frame_start = firstKFN
bpy.data.scenes[0].frame_end = lastKFN
print("This is Animated Model")
else:
print("This is Static Model")
skeleton.name = "Armature"
mesh.name = "body"
export_animated_mesh(gltf_path, IsAnimation=False)