fbx格式转换

目录

fbx查看器

fbx转bvh

fbx转bvh github总结待测

bvh转fbx

npz转换为fbx

npz转换为fbx代码:

convert2fbx.py


fbx查看器

fbx-review-installer.exe

https://www.autodesk.com/products/fbx/fbx-review

Autodesk FBX Review - Windows 64 Bit | Maya | Autodesk App Store

fbx转bvh

https://github.com/SinMDM/SinMDM/blob/0296efba20ae5875b6f1c092d277ea274e8ceda2/utils/fbx2bvh.py

"""
This code is a variation of https://github.com/rubenvillegas/cvpr2018nkn/blob/master/datasets/fbx2bvh.py
"""
from glob import glob
import os
import os.path as osp

import bpy

print('start')
in_dir = '<path to folder containing fbx file>'
out_dir = in_dir + '/fbx2bvh'  # in_dir.replace('fbx', 'bvh')
fbx_files = glob(osp.join(in_dir, '*.fbx'))
for idx, in_file in enumerate(fbx_files):
    print(in_file)
    in_file_no_path = osp.split(in_file)[1]
    motion_name = osp.splitext(in_file_no_path)[0]
    rel_in_file = osp.relpath(in_file, in_dir)
    rel_out_file = osp.join(osp.split(rel_in_file)[0], '{}'.format(motion_name), '{}.bvh'.format(motion_name))
    rel_dir = osp.split(rel_out_file)[0]
    out_file = osp.join(out_dir, rel_out_file)

    os.makedirs(osp.join(out_dir, rel_dir), exist_ok=True)

    bpy.ops.import_scene.fbx(filepath=in_file)

    action = bpy.data.actions[-1]
    assert action.frame_range[0] < 9999 and action.frame_range[1] > -9999  # checking because of Kfir's code
    bpy.ops.export_anim.bvh(filepath=out_file,
                            frame_start=action.frame_range[0],
                            frame_end=action.frame_range[1], root_transform_only=True)
    bpy.data.actions.remove(bpy.data.actions[-1])

    print('{} processed. #{} of {}'.format(out_file, idx, len(fbx_files)))

fbx转bvh github总结待测

bl_info = {
	"name": "Unity FBX format",
	"author": "Angel 'Edy' Garcia (@VehiclePhysics)",
	"version": (1, 4, 1),
	"blender": (2, 80, 0),
	"location": "File > Export > Unity FBX",
	"description": "FBX exporter compatible with Unity's coordinate and scaling system.",
	"warning": "",
	"wiki_url": "",
	"category": "Import-Export",
}


import bpy
import mathutils
import math


# Multi-user datablocks are preserved here. Unique copies are made for applying the rotation.
# Eventually multi-user datablocks become single-user and gets processed.
# Therefore restoring the multi-user data assigns a shared but already processed datablock.
shared_data = dict()

# All objects and collections in this view layer must be visible while being processed.
# apply_rotation and matrix changes don't have effect otherwise.
# Visibility will be restored right before saving the FBX.
hidden_collections = []
hidden_objects = []
disabled_collections = []
disabled_objects = []


def unhide_collections(col):
	global hidden_collections
	global disabled_collections

	# No need to unhide excluded collections. Their objects aren't included in current view layer.
	if col.exclude:
		return

	# Find hidden child collections and unhide them
	hidden = [item for item in col.children if not item.exclude and item.hide_viewport]
	for item in hidden:
		item.hide_viewport = False

	# Add them to the list so they could be restored later
	hidden_collections.extend(hidden)

	# Same with the disabled collections
	disabled = [item for item in col.children if not item.exclude and item.collection.hide_viewport]
	for item in disabled:
		item.collection.hide_viewport = False

	disabled_collections.extend(disabled)

	# Recursively unhide child collections
	for item in col.children:
		unhide_collections(item)


def unhide_objects():
	global hidden_objects
	global disabled_objects

	view_layer_objects = [ob for ob in bpy.data.objects if ob.name in bpy.context.view_layer.objects]

	for ob in view_layer_objects:
		if ob.hide_get():
			hidden_objects.append(ob)
			ob.hide_set(False)
		if ob.hide_viewport:
			disabled_objects.append(ob)
			ob.hide_viewport = False


def make_single_user_data():
	global shared_data

	for ob in bpy.data.objects:
		if ob.data and ob.data.users > 1:
			# Figure out actual users of this datablock (not counting fake users)
			users = [user for user in bpy.data.objects if user.data == ob.data]
			if len(users) > 1:
				# Store shared mesh data (MESH objects only).
				# Other shared datablocks (CURVE, FONT, etc) are always exported as separate meshes
				# by the built-in FBX exporter.
				if ob.type == 'MESH':
					# Shared mesh data will be restored if users have no active modifiers
					modifiers = 0
					for user in users:
						modifiers += len([mod for mod in user.modifiers if mod.show_viewport])
					if modifiers == 0:
						shared_data[ob.name] = ob.data

				# Single-user data is mandatory in all object types, otherwise we can't apply the rotation.
				ob.data = ob.data.copy()


def apply_object_modifiers():
	# Select objects in current view layer not using an armature modifier
	bpy.ops.object.select_all(action='DESELECT')
	for ob in bpy.data.objects:
		if ob.name in bpy.context.view_layer.objects:
			bypass_modifiers = False
			for mod in ob.modifiers:
				if mod.type == 'ARMATURE':
					bypass_modifiers = True
			if not bypass_modifiers:
				ob.select_set(True)

	# Conversion to mesh may not be available depending on the remaining objects
	if bpy.ops.object.convert.poll():
		print("Converting to meshes:", bpy.context.selected_objects)
		bpy.ops.object.convert(target='MESH')


def reset_parent_inverse(ob):
	if (ob.parent):
		mat_world = ob.matrix_world.copy()
		ob.matrix_parent_inverse.identity()
		ob.matrix_basis = ob.parent.matrix_world.inverted() @ mat_world


def apply_rotation(ob):
	bpy.ops.object.select_all(action='DESELECT')
	ob.select_set(True)
	bpy.ops.object.transform_apply(location = False, rotation = True, scale = False)


def fix_object(ob):
	# Only fix objects in current view layer
	if ob.name in bpy.context.view_layer.objects:

		# Reset parent's inverse so we can work with local transform directly
		reset_parent_inverse(ob)

		# Create a copy of the local matrix and set a pure X-90 matrix
		mat_original = ob.matrix_local.copy()
		ob.matrix_local = mathutils.Matrix.Rotation(math.radians(-90.0), 4, 'X')

		# Apply the rotation to the object
		apply_rotation(ob)

		# Reapply the previous local transform with an X+90 rotation
		ob.matrix_local = mat_original @ mathutils.Matrix.Rotation(math.radians(90.0), 4, 'X')

	# Recursively fix child objects in current view layer.
	# Children may be in the current view layer even if their parent isn't.
	for child in ob.children:
		fix_object(child)


def export_unity_fbx(context, filepath, active_collection, selected_objects, deform_bones, leaf_bones, primary_bone_axis, secondary_bone_axis, tangent_space, triangulate_faces):
	global shared_data
	global hidden_collections
	global hidden_objects
	global disabled_collections
	global disabled_objects

	print("Preparing 3D model for Unity...")

	# Root objects: Empty, Mesh, Curve, Surface, Font or Armature without parent
	root_objects = [item for item in bpy.data.objects if (item.type == "EMPTY" or item.type == "MESH" or item.type == "ARMATURE" or item.type == "FONT" or item.type == "CURVE" or item.type == "SURFACE") and not item.parent]

	# Preserve current scene
	# undo_push examples, including exporters' execute:
	# https://programtalk.com/python-examples/bpy.ops.ed.undo_push  (Examples 4, 5 and 6)
	# https://sourcecodequery.com/example-method/bpy.ops.ed.undo  (Examples 1 and 2)

	bpy.ops.ed.undo_push(message="Prepare Unity FBX")

	shared_data = dict()
	hidden_collections = []
	hidden_objects = []
	disabled_collections = []
	disabled_objects = []

	selection = bpy.context.selected_objects

	# Object mode
	if bpy.ops.object.mode_set.poll():
		bpy.ops.object.mode_set(mode="OBJECT")

	# Ensure all the collections and objects in this view layer are visible
	unhide_collections(bpy.context.view_layer.layer_collection)
	unhide_objects()

	# Create a single copy in multi-user datablocks. Will be restored after fixing rotations.
	make_single_user_data()

	# Apply modifiers to objects (except those affected by an armature)
	apply_object_modifiers()

	try:
		# Fix rotations
		for ob in root_objects:
			print(ob.name, ob.type)
			fix_object(ob)

		# Restore multi-user meshes
		for item in shared_data:
			bpy.data.objects[item].data = shared_data[item]

		# Recompute the transforms out of the changed matrices
		bpy.context.view_layer.update()

		# Restore hidden and disabled objects
		for ob in hidden_objects:
			ob.hide_set(True)
		for ob in disabled_objects:
			ob.hide_viewport = True

		# Restore hidden and disabled collections
		for col in hidden_collections:
			col.hide_viewport = True
		for col in disabled_collections:
			col.collection.hide_viewport = True

		# Restore selection
		bpy.ops.object.select_all(action='DESELECT')
		for ob in selection:
			ob.select_set(True)

		# Export FBX file
		params = dict(filepath=filepath, apply_scale_options='FBX_SCALE_UNITS', object_types={'EMPTY', 'MESH', 'ARMATURE'}, use_active_collection=active_collection, use_selection=selected_objects, use_armature_deform_only=deform_bones, add_leaf_bones=leaf_bones, primary_bone_axis=primary_bone_axis, secondary_bone_axis=secondary_bone_axis, use_tspace=tangent_space, use_triangles=triangulate_faces)

		print("Invoking default FBX Exporter:", params)
		bpy.ops.export_scene.fbx(**params)

	except Exception as e:
		bpy.ops.ed.undo_push(message="")
		bpy.ops.ed.undo()
		bpy.ops.ed.undo_push(message="Export Unity FBX")
		print(e)
		print("File not saved.")
		# Always finish with 'FINISHED' so Undo is handled properly
		return {'FINISHED'}

	# Restore scene and finish

	bpy.ops.ed.undo_push(message="")
	bpy.ops.ed.undo()
	bpy.ops.ed.undo_push(message="Export Unity FBX")
	print("FBX file for Unity saved.")
	return {'FINISHED'}


#---------------------------------------------------------------------------------------------------
# Exporter stuff (from the Operator File Export template)

# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator


class ExportUnityFbx(Operator, ExportHelper):
	"""FBX exporter compatible with Unity's coordinate and scaling system"""
	bl_idname = "export_scene.unity_fbx"
	bl_label = "Export Unity FBX"
	bl_options = {'UNDO_GROUPED'}

	# ExportHelper mixin class uses this
	filename_ext = ".fbx"

	filter_glob: StringProperty(
		default="*.fbx",
		options={'HIDDEN'},
		maxlen=255,  # Max internal buffer length, longer would be clamped.
	)

	# List of operator properties, the attributes will be assigned
	# to the class instance from the operator settings before calling.

	active_collection: BoolProperty(
		name="Active Collection Only",
		description="Export objects in the active collection only (and its children). May be combined with Selected Objects Only",
		default=False,
	)

	selected_objects: BoolProperty(
		name="Selected Objects Only",
		description="Export selected objects only. May be combined with Active Collection Only",
		default=False,
	)

	deform_bones: BoolProperty(
		name="Only Deform Bones",
		description="Only write deforming bones (and non-deforming ones when they have deforming children)",
		default=False,
	)

	leaf_bones: BoolProperty(
		name="Add Leaf Bones",
		description="Append a final bone to the end of each chain to specify last bone length (use this when you intend to edit the armature from exported data)",
		default=False,
	)

	primary_bone_axis: EnumProperty(
		name="Primary",
		items=(('X', "X Axis", ""),
				('Y', "Y Axis", ""),
				('Z', "Z Axis", ""),
				('-X', "-X Axis", ""),
				('-Y', "-Y Axis", ""),
				('-Z', "-Z Axis", ""),
		),
		default='Y',
	)

	secondary_bone_axis: EnumProperty(
		name="Secondary",
		items=(('X', "X Axis", ""),
				('Y', "Y Axis", ""),
				('Z', "Z Axis", ""),
				('-X', "-X Axis", ""),
				('-Y', "-Y Axis", ""),
				('-Z', "-Z Axis", ""),
		),
		default='X',
	)

	tangent_space: BoolProperty(
		name="Export tangents",
		description="Add binormal and tangent vectors, together with normal they form the tangent space (tris/quads only). Meshes with N-gons won't export tangents unless the option Triangulate Faces is enabled",
		default=False,
	)

	triangulate_faces: BoolProperty(
		name="Triangulate Faces",
		description="Convert all faces to triangles. This is necessary for exporting tangents in meshes with N-gons. Otherwise Unity will show a warning when importing tangents in these meshes",
		default=False,
	)

	# Custom draw method
	# https://blender.stackexchange.com/questions/55437/add-gui-elements-to-exporter-window
	# https://docs.blender.org/api/current/bpy.types.UILayout.html

	def draw(self, context):
		layout = self.layout
		layout.row().label(text = "Selection")
		layout.row().prop(self, "active_collection")
		layout.row().prop(self, "selected_objects")

		layout.separator()
		layout.row().label(text = "Meshes")
		layout.row().prop(self, "tangent_space")
		layout.row().prop(self, "triangulate_faces")

		layout.separator()
		layout.row().label(text = "Armatures")
		layout.row().prop(self, "deform_bones")
		layout.row().prop(self, "leaf_bones")

		layout.row().label(text = "Bone Axes")
		split = layout.split(factor=0.4)
		col = split.column()
		col.alignment = 'RIGHT'
		col.label(text = "Primary")
		split.column().prop(self, "primary_bone_axis", text="")
		split = layout.split(factor=0.4)
		col = split.column()
		col.alignment = 'RIGHT'
		col.label(text = "Secondary")
		split.column().prop(self, "secondary_bone_axis", text="")

	def execute(self, context):
		return export_unity_fbx(context, self.filepath, self.active_collection, self.selected_objects, self.deform_bones, self.leaf_bones, self.primary_bone_axis, self.secondary_bone_axis, self.tangent_space, self.triangulate_faces)


# Only needed if you want to add into a dynamic menu
def menu_func_export(self, context):
	self.layout.operator(ExportUnityFbx.bl_idname, text="Unity FBX (.fbx)")


def register():
	bpy.utils.register_class(ExportUnityFbx)
	bpy.types.TOPBAR_MT_file_export.append(menu_func_export)


def unregister():
	bpy.utils.unregister_class(ExportUnityFbx)
	bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)


if __name__ == "__main__":
	register()

	# test call
	bpy.ops.export_scene.unity_fbx('INVOKE_DEFAULT')

bvh转fbx

npy库:

import os.path

import bpy
import sys


bvh_in = r'D:\project\MHFormer-pose\baitao.bvh'
bvh_in = r'D:\project\MHFormer-pose\9_0122_mh.bvh'
fbx_out = "aa.fbx"


filename=os.path.basename(bvh_in)

file_name_with_extension = os.path.basename(bvh_in)

# 使用 splitext 分割文件名和后缀
filename, file_extension = os.path.splitext(file_name_with_extension)

fbx_out = filename+".fbx"
# Import the BVH file
# See https://docs.blender.org/api/current/bpy.ops.import_anim.html?highlight=import_anim#module-bpy.ops.import_anim

bpy.ops.wm.read_factory_settings(use_empty=True)

bpy.ops.import_anim.bvh(filepath=bvh_in, filter_glob="*.bvh", global_scale=0.0001, frame_start=1, target='ARMATURE',
                        use_fps_scale=False, use_cyclic=False, rotate_mode='NATIVE', axis_forward='Z', axis_up='Y')
#
# # Export as FBX
# # See https://docs.blender.org/api/current/bpy.ops.export_scene.html
# bpy.ops.export_scene.fbx(filepath=fbx_out, axis_forward='Z',
#                          axis_up='Y', use_selection=True, apply_scale_options='FBX_SCALE_NONE')



# bpy.ops.import_anim.bvh(filepath=bvh_in, update_scene_fps=True, update_scene_duration=True)

# Ensure correct scene frame range
assert len(bpy.data.actions) == 1
frame_range = bpy.data.actions[0].frame_range
bpy.context.scene.frame_start = int(frame_range[0])
bpy.context.scene.frame_end = int(frame_range[1])

# Add some mesh data so UE allows import
bpy.data.objects[filename].name = "animation"
bpy.ops.mesh.primitive_plane_add()
bpy.data.objects["Plane"].name = filename
bpy.data.objects["animation"].select_set(True)
bpy.ops.object.parent_set()  # Parent the "animation" object to cube

# Export
bpy.ops.export_scene.fbx(filepath=fbx_out, check_existing=False)

npz转换为fbx

https://github.com/Arthur151/ROMP/blob/4eebd3647f57d291d26423e51f0d514ff7197cb3/simple_romp/tools/convert2fbx.py

依赖项

mathutils

pip install mathutils

npz转换为fbx代码:

需要两个模板:

male_model_path = './models/SMPL_m_unityDoubleBlends_lbs_10_scale5_207_v1.0.0.fbx'
female_model_path = './models/SMPL_f_unityDoubleBlends_lbs_10_scale5_207_v1.0.0.fbx'

要求npz中有字典results和sequence_results

frame_results = np.load(input_path, allow_pickle=True)['results'][()] sequence_results = np.load(input_path, allow_pickle=True)['sequence_results'][()]

convert2fbx.py

import os
import sys
import time
import argparse
import numpy as np
from math import radians

try:
    import bpy
except:
    print('Missing bpy, install via pip, please install bpy by yourself if failed.')
    os.system('pip install future-fstrings')
    os.system('pip install tools/bpy-2.82.1 && post_install')
    import bpy
try:
    from mathutils import Matrix, Vector, Quaternion, Euler
except:
    os.system('pip install mathutils==2.81.2')
    from mathutils import Matrix, Vector, Quaternion, Euler

# Globals
# Add your UNIX paths here!
male_model_path = './models/SMPL_m_unityDoubleBlends_lbs_10_scale5_207_v1.0.0.fbx'
female_model_path = './models/SMPL_f_unityDoubleBlends_lbs_10_scale5_207_v1.0.0.fbx'
character_model_path = None

'''
python tools/convert2fbx.py --input=/home/yusun/BEV_results/video_results.npz --output=/home/yusun/BEV_results/dance.fbx --gender=female
'''

fps_source = 24
fps_target = 24

gender = 'male'  # female

support_formats = ['.fbx', '.glb', '.bvh']

bone_name_from_index = {0: 'Pelvis', 1: 'L_Hip', 2: 'R_Hip', 3: 'Spine1', 4: 'L_Knee', 5: 'R_Knee', 6: 'Spine2', 7: 'L_Ankle', 8: 'R_Ankle', 9: 'Spine3', 10: 'L_Foot', 11: 'R_Foot', 12: 'Neck', 13: 'L_Collar', 14: 'R_Collar', 15: 'Head', 16: 'L_Shoulder', 17: 'R_Shoulder', 18: 'L_Elbow',
    19: 'R_Elbow', 20: 'L_Wrist', 21: 'R_Wrist', 22: 'L_Hand', 23: 'R_Hand'}

# To use other avatar for animation, please define the corresponding 3D skeleton like this.
bone_name_from_index_character = {0: 'Hips', 1: 'RightUpLeg', 2: 'LeftUpLeg', 3: 'Spine', 4: 'RightLeg', 5: 'LeftLeg', 6: 'Spine1', 7: 'RightFoot', 8: 'LeftFoot', 9: 'Spine2', 10: 'LeftToeBase', 11: 'RightToeBase', 12: 'Neck', 13: 'LeftHandIndex1', 14: 'RightHandIndex1', 15: 'Head',
    16: 'LeftShoulder', 17: 'RightShoulder', 18: 'LeftArm', 19: 'RightArm', 20: 'LeftForeArm', 21: 'RightForeArm', 22: 'LeftHand', 23: 'RightHand'}


# Helper functions

# Computes rotation matrix through Rodrigues formula as in cv2.Rodrigues
# Source: smpl/plugins/blender/corrective_bpy_sh.py
def Rodrigues(rotvec):
    theta = np.linalg.norm(rotvec)
    r = (rotvec / theta).reshape(3, 1) if theta > 0. else rotvec
    cost = np.cos(theta)
    mat = np.asarray([[0, -r[2], r[1]], [r[2], 0, -r[0]], [-r[1], r[0], 0]])
    return (cost * np.eye(3) + (1 - cost) * r.dot(r.T) + np.sin(theta) * mat)


# Setup scene
def setup_scene(model_path, fps_target):
    scene = bpy.data.scenes['Scene']

    ###########################
    # Engine independent setup
    ###########################

    scene.render.fps = fps_target

    # Remove default cube
    if 'Cube' in bpy.data.objects:
        bpy.data.objects['Cube'].select_set(True)
        bpy.ops.object.delete()

    # Import gender specific .fbx template file
    bpy.ops.import_scene.fbx(filepath=model_path)


# Process single pose into keyframed bone orientations
def process_pose(current_frame, pose, trans, pelvis_position):
    if pose.shape[0] == 72:
        rod_rots = pose.reshape(24, 3)
    else:
        rod_rots = pose.reshape(26, 3)

    mat_rots = [Rodrigues(rod_rot) for rod_rot in rod_rots]

    # Set the location of the Pelvis bone to the translation parameter
    armature = bpy.data.objects['Armature']
    bones = armature.pose.bones

    # Pelvis: X-Right, Y-Up, Z-Forward (Blender -Y)
    root_location = Vector((100 * trans[1], 100 * trans[2], 100 * trans[0])) - pelvis_position
    # Set absolute pelvis location relative to Pelvis bone head
    bones[bone_name_from_index[0]].location = root_location

    # bones['Root'].location = Vector(trans)
    bones[bone_name_from_index[0]].keyframe_insert('location', frame=current_frame)

    for index, mat_rot in enumerate(mat_rots, 0):
        if index >= 24:
            continue

        bone = bones[bone_name_from_index[index]]

        bone_rotation = Matrix(mat_rot).to_quaternion()
        quat_x_90_cw = Quaternion((1.0, 0.0, 0.0), radians(-90))
        # quat_x_n135_cw = Quaternion((1.0, 0.0, 0.0), radians(-135))
        # quat_x_p45_cw = Quaternion((1.0, 0.0, 0.0), radians(45))
        # quat_y_90_cw = Quaternion((0.0, 1.0, 0.0), radians(-90))
        quat_z_90_cw = Quaternion((0.0, 0.0, 1.0), radians(-90))

        if index == 0:
            # Rotate pelvis so that avatar stands upright and looks along negative Y avis
            bone.rotation_quaternion = (quat_x_90_cw @ quat_z_90_cw) @ bone_rotation
        else:
            bone.rotation_quaternion = bone_rotation

        bone.keyframe_insert('rotation_quaternion', frame=current_frame)

    return


# Process all the poses from the pose file
def process_poses(input_path, gender, fps_source, fps_target, subject_id=-1):
    print('Processing: ' + input_path)

    frame_results = np.load(input_path, allow_pickle=True)['results'][()]
    sequence_results = np.load(input_path, allow_pickle=True)['sequence_results'][()]

    poses, trans = [], []

    if len(sequence_results) > 0:
        subject_ids = list(sequence_results.keys())
        if subject_id == -1 or subject_id not in subject_ids:
            print('Get motion sequence with subject IDs:', subject_ids)
            subject_id = int(input('Please select one subject ID (int):'))
        poses = np.array(sequence_results[subject_id]['smpl_thetas'])
        trans = np.array(sequence_results[subject_id]['cam_trans'])
    else:
        print('Missing tracking IDs in results. Using the first pose results for animation.')
        print('To get the tracking IDs, please use temporal optimization during inference.')
        frame_names = sorted(list(frame_results.keys()))
        poses, trans = np.zeros((len(frame_names), 72)), np.zeros((len(frame_names), 3))
        for inds, frame_name in enumerate(frame_names):
            poses[inds] = frame_results[frame_name]['smpl_thetas'][0]
            trans[inds] = frame_results[frame_name]['cam_trans'][0]

    if gender == 'female':
        model_path = female_model_path
        for k, v in bone_name_from_index.items():
            bone_name_from_index[k] = 'f_avg_' + v
    elif gender == 'male':
        model_path = male_model_path
        for k, v in bone_name_from_index.items():
            bone_name_from_index[k] = 'm_avg_' + v
    elif gender == 'character':
        model_path = character_model_path
        for k, v in bone_name_from_index_character.items():
            bone_name_from_index[k] = 'mixamorig1:' + v
    else:
        print('ERROR: Unsupported gender: ' + gender)
        sys.exit(1)

    # Limit target fps to source fps
    if fps_target > fps_source:
        fps_target = fps_source

    print('Gender:', gender)
    print('Number of source poses: ', poses.shape[0])
    print('Source frames-per-second: ', fps_source)
    print('Target frames-per-second: ', fps_target)
    print('--------------------------------------------------')

    setup_scene(model_path, fps_target)

    scene = bpy.data.scenes['Scene']
    sample_rate = int(fps_source / fps_target)
    scene.frame_end = (int)(poses.shape[0] / sample_rate)

    # Retrieve pelvis world position.
    # Unit is [cm] due to Armature scaling.
    # Need to make copy since reference will change when bone location is modified.
    armaturee = bpy.data.armatures[0]
    ob = bpy.data.objects['Armature']
    armature = ob.data

    bpy.ops.object.mode_set(mode='EDIT')
    # get specific bone name 'Bone'
    pelvis_bone = armature.edit_bones[bone_name_from_index[0]]
    # pelvis_bone = armature.edit_bones['f_avg_Pelvis']
    pelvis_position = Vector(pelvis_bone.head)
    bpy.ops.object.mode_set(mode='OBJECT')

    source_index = 0
    frame = 1

    offset = np.array([0.0, 0.0, 0.0])

    while source_index < poses.shape[0]:
        print('Adding pose: ' + str(source_index))

        # Go to new frame
        scene.frame_set(frame)

        process_pose(frame, poses[source_index], (trans[source_index] - offset), pelvis_position)
        source_index += sample_rate
        frame += 1

    return frame


def rotate_armature(use):
    if use == True:
        # Switch to Pose Mode
        bpy.ops.object.posemode_toggle()

        # Find the Armature & Bones
        ob = bpy.data.objects['Armature']
        armature = ob.data
        bones = armature.bones
        rootbone = bones[0]

        # Find the Root bone
        for bone in bones:
            if "avg_root" in bone.name:
                rootbone = bone

        rootbone.select = True

        # Rotate the Root bone by 90 euler degrees on the Y axis. Set --rotate_Y=False if the rotation is not needed.
        bpy.ops.transform.rotate(value=1.5708, orient_axis='Y', orient_type='GLOBAL', orient_matrix=((1, 0, 0), (0, 1, 0), (0, 0, 1)), orient_matrix_type='GLOBAL', constraint_axis=(False, True, False), mirror=True, use_proportional_edit=False, proportional_edit_falloff='SMOOTH', proportional_size=1,
            use_proportional_connected=False, use_proportional_projected=False, release_confirm=True)
        # Revert back to Object Mode
        bpy.ops.object.posemode_toggle()


def export_animated_mesh(output_path):
    # Create output directory if needed
    output_dir = os.path.dirname(output_path)
    if not os.path.isdir(output_dir):
        os.makedirs(output_dir, exist_ok=True)

    # Fix Rotation
    rotate_armature(args.rotate_y)

    # Select only skinned mesh and rig
    bpy.ops.object.select_all(action='DESELECT')
    bpy.data.objects['Armature'].select_set(True)
    bpy.data.objects['Armature'].children[0].select_set(True)

    if output_path.endswith('.glb'):
        print('Exporting to glTF binary (.glb)')
        # Currently exporting without shape/pose shapes for smaller file sizes
        bpy.ops.export_scene.gltf(filepath=output_path, export_format='GLB', export_selected=True, export_morph=False)
    elif output_path.endswith('.fbx'):
        print('Exporting to FBX binary (.fbx)')
        bpy.ops.export_scene.fbx(filepath=output_path, use_selection=True, add_leaf_bones=False)
    elif output_path.endswith('.bvh'):
        bpy.ops.export_anim.bvh(filepath=output_path, root_transform_only=False)
    else:
        print('ERROR: Unsupported export format: ' + output_path)
        sys.exit(1)

    return


if __name__ == '__main__':
    if bpy.app.background:
        parser = argparse.ArgumentParser(description='Create keyframed animated skinned SMPL mesh from VIBE output')
        # parser.add_argument('--input', dest='input_path', type=str, default=r'D:\project\MHFormer-pose\output\9\output_3D\output_keypoints_3d.npz',  # '../demo/videos/sample_video2_results.npz',
        parser.add_argument('--input', dest='input_path', type=str, default=r'D:\project\MHFormer-pose\output\baitao\output_3D\output_keypoints_3d.npz',  # '../demo/videos/sample_video2_results.npz',
            help='Input file or directory')
        parser.add_argument('--output', dest='output_path', type=str, default='key_3d.fbx',  # '../demo/videos/sample_video2.fbx',
            help='Output file or directory')
        parser.add_argument('--fps_source', type=int, default=fps_source, help='Source framerate')
        parser.add_argument('--fps_target', type=int, default=fps_target, help='Target framerate')
        parser.add_argument('--gender', type=str, default=gender, help='Always use specified gender')
        parser.add_argument('--subject_id', type=int, default=-1, help='Detected person ID to use for fbx animation')
        parser.add_argument('--rotate_y', type=bool, default=True, help='whether to rotate the root bone on the Y axis by -90 on export. Otherwise it may be rotated incorrectly')

        args = parser.parse_args()

        input_path = args.input_path
        output_path = args.output_path

        print('Input path: ' + input_path)
        print('Output path: ' + output_path)

        if not os.path.exists(input_path):
            print('ERROR: Invalid input path')
            sys.exit(1)

        fps_source = args.fps_source
        fps_target = args.fps_target
        gender = args.gender

    startTime = time.perf_counter()
    cwd = os.getcwd()
    # Turn relative input/output paths into absolute paths
    if not input_path.startswith(os.path.sep):
        input_path = os.path.join(cwd, input_path)
    if not output_path.startswith(os.path.sep):
        output_path = os.path.join(cwd, output_path)

    if os.path.splitext(output_path)[1] not in support_formats:
        print('ERROR: Invalid output format, we only support', support_formats)
        sys.exit(1)

    # Process pose file
    poses_processed = process_poses(input_path=input_path, gender=gender, fps_source=fps_source, fps_target=fps_target, subject_id=args.subject_id)
    export_animated_mesh(output_path)

    print('--------------------------------------------------')
    print('Animation export finished, save to ', output_path)
    print('Poses processed: ', poses_processed)
    print('Processing time : ', time.perf_counter() - startTime)
    print('--------------------------------------------------')

  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI算法加油站

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值