| 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:] |
| except ValueError as e: |
| return [] |
|
|
| |
| 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: |
| |
| keyframe_list.append(key.co[0]) |
|
|
| keyframe_list = list(set(keyframe_list)) |
| print('keyframe_list:') |
| |
| print(len(keyframe_list)) |
| firstKFN = int(keyframe_list[0]) |
| lastKFN = int(keyframe_list[-1]) |
| |
| 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() |
| |
| 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") |
| |
| 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() |
| |
| 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): |
| |
| 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() |
|
|
| |
| 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 |
|
|
| |
| 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: |
| |
| 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) |
|
|