目录
文件拖拽导入
smpl导入导出
好像可以导入动画
smpl_blender_addon导入一帧
保存pose
导入导出完整代码
文件拖拽导入
https://github.com/mika-f/blender-drag-and-drop
支持格式:
*.abc
*.bvh
*.dae
*.fbx
*.glb
*.gltf
*.obj
*.ply
*.stl
*.svg
*.usd
*.usda
*.usdc
*.vrm
(Required VRM Add-on for Blender)*.x3d
*.wrl
smpl导入导出
好像可以导入动画
https://github.com/vltmedia/QuickMocap-BlenderAddon
smpl_blender_addon导入一帧
这个也是一次只能导入一帧,不能导入动画
https://github.com/Meshcapade/SMPL_blender_addon
这个可以写pose,就是把旋转角度保存下来
class SMPLSnapGroundPlane(bpy.types.Operator):bl_idname = "object.smpl_snap_ground_plane"bl_label = "Snap To Ground Plane"bl_description = ("Snaps mesh to the XY ground plane")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh or armature is active objectreturn ((context.object.type == 'MESH') or (context.object.type == 'ARMATURE'))except: return Falsedef execute(self, context):bpy.ops.object.mode_set(mode='OBJECT')obj = bpy.context.objectif obj.type == 'ARMATURE':armature = objobj = bpy.context.object.children[0]else:armature = obj.parent# Get vertices with applied skin modifier in object coordinatesdepsgraph = context.evaluated_depsgraph_get()object_eval = obj.evaluated_get(depsgraph)mesh_from_eval = object_eval.to_mesh()# Get vertices in world coordinatesmatrix_world = obj.matrix_worldvertices_world = [matrix_world @ vertex.co for vertex in mesh_from_eval.vertices]z_min = (min(vertices_world, key=lambda item: item.z)).zobject_eval.to_mesh_clear() # Remove temporary mesh# Translate armature edit bonescontext.view_layer.objects.active = armaturebpy.ops.object.mode_set(mode='EDIT')for edit_bone in armature.data.edit_bones:if edit_bone.name != "root":edit_bone.translate(Vector((0.0, 0.0, -z_min)))# Translate skinned mesh and apply translationbpy.ops.object.mode_set(mode='OBJECT')context.view_layer.objects.active = objobj.location = (0.0, 0.0, -z_min)bpy.ops.object.transform_apply(location = True)return {'FINISHED'}
保存pose
class SMPLWritePose(bpy.types.Operator):bl_idname = "object.smpl_write_pose"bl_label = "Write Pose1"bl_description = ("Writes SMPL pose thetas to console window")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh or armature is active objectreturn (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')except: return Falsedef execute(self, context):obj = bpy.context.objectif obj.type == 'MESH':armature = obj.parentelse:armature = obj# Get armature pose in rodrigues representationpose = [0.0] * (len(SMPL_JOINT_NAMES) * 3)for index in range(len(SMPL_JOINT_NAMES)):joint_name = SMPL_JOINT_NAMES[index]joint_pose = rodrigues_from_pose(armature, joint_name)pose[index*3 + 0] = joint_pose[0]pose[index*3 + 1] = joint_pose[1]pose[index*3 + 2] = joint_pose[2]print("pose = " + str(pose))npz_file="1234.npz"np.savez_compressed(npz_file, joints_3d={"data": pose})return {'FINISHED'}
导入导出完整代码
这个可以导入导出,代码没有报错了,但是加载后没有显示出来,而且只能保存一帧,不能保存动画
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
import loggingbl_info = {"name": "SMPL for Blender","author": "Joachim Tesch, Max Planck Institute for Intelligent Systems","version": (2021, 6, 11),"blender": (2, 80, 0),"location": "Viewport > Right panel","description": "SMPL for Blender","wiki_url": "https://smpl.is.tue.mpg.de/","category": "SMPL"}import bpy
import bmesh
from bpy_extras.io_utils import ExportHelper # ExportHelper is a helper class, defines filename and invoke() function which calls the file selector.from mathutils import Vector, Quaternion
from math import radians
import numpy as np
import os
import picklefrom bpy.props import ( BoolProperty, EnumProperty, FloatProperty, PointerProperty )
from bpy.types import ( PropertyGroup )# SMPL globals
SMPL_JOINT_NAMES = {0: 'Pelvis',1: 'L_Hip', 4: 'L_Knee', 7: 'L_Ankle', 10: 'L_Foot',2: 'R_Hip', 5: 'R_Knee', 8: 'R_Ankle', 11: 'R_Foot',3: 'Spine1', 6: 'Spine2', 9: 'Spine3', 12: 'Neck', 15: 'Head',13: 'L_Collar', 16: 'L_Shoulder', 18: 'L_Elbow', 20: 'L_Wrist', 22: 'L_Hand',14: 'R_Collar', 17: 'R_Shoulder', 19: 'R_Elbow', 21: 'R_Wrist', 23: 'R_Hand',
}
smpl_joints = len(SMPL_JOINT_NAMES)
# End SMPL globalsdef rodrigues_from_pose(armature, bone_name):# Ensure that rotation mode is AXIS_ANGLE so the we get a correct readout of current posearmature.pose.bones[bone_name].rotation_mode = 'AXIS_ANGLE'axis_angle = armature.pose.bones[bone_name].rotation_axis_angleangle = axis_angle[0]rodrigues = Vector((axis_angle[1], axis_angle[2], axis_angle[3]))rodrigues.normalize()rodrigues = rodrigues * anglereturn rodriguesdef update_corrective_poseshapes(self, context):if self.smpl_corrective_poseshapes:bpy.ops.object.smpl_set_poseshapes('EXEC_DEFAULT')else:bpy.ops.object.smpl_reset_poseshapes('EXEC_DEFAULT')# Property groups for UI
class PG_SMPLProperties(PropertyGroup):smpl_gender: EnumProperty(name = "Model",description = "SMPL model",items = [ ("female", "Female", ""), ("male", "Male", "") ])smpl_texture: EnumProperty(name = "",description = "SMPL model texture",items = [ ("NONE", "None", ""), ("UV_GRID", "UV Grid", ""), ("COLOR_GRID", "Color Grid", "") ])smpl_corrective_poseshapes: BoolProperty(name = "Corrective Pose Shapes",description = "Enable/disable corrective pose shapes of SMPL model",update = update_corrective_poseshapes)smpl_export_setting_shape_keys: EnumProperty(name = "",description = "Blend shape export settings",items = [ ("SHAPE_POSE", "All: Shape + Posecorrectives", "Export shape keys for body shape and pose correctives"), ("SHAPE", "Reduced: Shape space only", "Export only shape keys for body shape"), ("NONE", "None: Apply shape space", "Do not export any shape keys, shape keys for body shape will be baked into mesh") ],)class SMPLAddGender(bpy.types.Operator):bl_idname = "scene.smpl_add_gender"bl_label = "Add"bl_description = ("Add SMPL model of selected gender to scene")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if in Object Modeif (context.active_object is None) or (context.active_object.mode == 'OBJECT'):return Trueelse: return Falseexcept: return Falsedef execute(self, context):gender = context.window_manager.smpl_tool.smpl_genderprint("Adding gender: " + gender)path = os.path.dirname(os.path.realpath(__file__))objects_path = os.path.join(path, "data", "smpl-model-20200803.blend", "Object")object_name = "SMPL-mesh-" + genderbpy.ops.wm.append(filename=object_name, directory=str(objects_path))# Select imported meshobject_name = context.selected_objects[0].namebpy.ops.object.select_all(action='DESELECT')context.view_layer.objects.active = bpy.data.objects[object_name]bpy.data.objects[object_name].select_set(True)return {'FINISHED'}class SMPLSetTexture(bpy.types.Operator):bl_idname = "scene.smpl_set_texture"bl_label = "Set"bl_description = ("Set selected texture")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if in active object is meshif (context.object.type == 'MESH'):return Trueelse:return Falseexcept: return Falsedef execute(self, context):texture = context.window_manager.smpl_tool.smpl_textureprint("Setting texture: " + texture)obj = bpy.context.objectif (len(obj.data.materials) == 0) or (obj.data.materials[0] is None):self.report({'WARNING'}, "Selected mesh has no material: %s" % obj.name)return {'CANCELLED'}mat = obj.data.materials[0]links = mat.node_tree.linksnodes = mat.node_tree.nodes# Find texture nodenode_texture = Nonefor node in nodes:if node.type == 'TEX_IMAGE':node_texture = nodebreak# Find shader nodenode_shader = Nonefor node in nodes:if node.type.startswith('BSDF'):node_shader = nodebreakif texture == 'NONE':# Unlink texture nodeif node_texture is not None:for link in node_texture.outputs[0].links:links.remove(link)nodes.remove(node_texture)# 3D Viewport still shows previous texture when texture link is removed via script.# As a workaround we trigger desired viewport update by setting color value.node_shader.inputs[0].default_value = node_shader.inputs[0].default_valueelse:if node_texture is None:node_texture = nodes.new(type="ShaderNodeTexImage")if texture == 'UV_GRID':if texture not in bpy.data.images:bpy.ops.image.new(name=texture, generated_type='UV_GRID')image = bpy.data.images[texture]else:if texture not in bpy.data.images:bpy.ops.image.new(name=texture, generated_type='COLOR_GRID')image = bpy.data.images[texture]node_texture.image = image# Link texture node to shader node if not already linkedif len(node_texture.outputs[0].links) == 0:links.new(node_texture.outputs[0], node_shader.inputs[0])return {'FINISHED'}class SMPLRandomShapes(bpy.types.Operator):bl_idname = "object.smpl_random_shapes"bl_label = "Random Shapes"bl_description = ("Sets all shape blend shape keys to a random value")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh is active objectreturn context.object.type == 'MESH'except: return Falsedef execute(self, context):obj = bpy.context.objectbpy.ops.object.mode_set(mode='OBJECT')for key_block in obj.data.shape_keys.key_blocks:if key_block.name.startswith("Shape"):key_block.value = np.random.normal(0.0, 1.0)bpy.ops.object.smpl_update_joint_locations('EXEC_DEFAULT')return {'FINISHED'}class SMPLResetShapes(bpy.types.Operator):bl_idname = "object.smpl_reset_shapes"bl_label = "Reset"bl_description = ("Resets all blend shape keys for shape")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh is active objectreturn context.object.type == 'MESH'except: return Falsedef execute(self, context):obj = bpy.context.objectbpy.ops.object.mode_set(mode='OBJECT')for key_block in obj.data.shape_keys.key_blocks:if key_block.name.startswith("Shape"):key_block.value = 0.0bpy.ops.object.smpl_update_joint_locations('EXEC_DEFAULT')return {'FINISHED'}class SMPLSnapGroundPlane(bpy.types.Operator):bl_idname = "object.smpl_snap_ground_plane"bl_label = "Snap To Ground Plane"bl_description = ("Snaps mesh to the XY ground plane")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh or armature is active objectreturn ((context.object.type == 'MESH') or (context.object.type == 'ARMATURE'))except: return Falsedef execute(self, context):bpy.ops.object.mode_set(mode='OBJECT')obj = bpy.context.objectif obj.type == 'ARMATURE':armature = objobj = bpy.context.object.children[0]else:armature = obj.parent# Get vertices with applied skin modifier in object coordinatesdepsgraph = context.evaluated_depsgraph_get()object_eval = obj.evaluated_get(depsgraph)mesh_from_eval = object_eval.to_mesh()# Get vertices in world coordinatesmatrix_world = obj.matrix_worldvertices_world = [matrix_world @ vertex.co for vertex in mesh_from_eval.vertices]z_min = (min(vertices_world, key=lambda item: item.z)).zobject_eval.to_mesh_clear() # Remove temporary mesh# Translate armature edit bonescontext.view_layer.objects.active = armaturebpy.ops.object.mode_set(mode='EDIT')for edit_bone in armature.data.edit_bones:if edit_bone.name != "root":edit_bone.translate(Vector((0.0, 0.0, -z_min)))# Translate skinned mesh and apply translationbpy.ops.object.mode_set(mode='OBJECT')context.view_layer.objects.active = objobj.location = (0.0, 0.0, -z_min)bpy.ops.object.transform_apply(location = True)return {'FINISHED'}class SMPLUpdateJointLocations(bpy.types.Operator):bl_idname = "object.smpl_update_joint_locations"bl_label = "Update Joint Locations"bl_description = ("Update joint locations after shape/expression changes")bl_options = {'REGISTER', 'UNDO'}j_regressor_male = Nonej_regressor_female = None@classmethoddef poll(cls, context):try:# Enable button only if mesh is active objectreturn ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE'))except: return Falsedef execute(self, context):obj = bpy.context.objectbpy.ops.object.mode_set(mode='OBJECT')if self.j_regressor_female is None:path = os.path.dirname(os.path.realpath(__file__))regressor_path = os.path.join(path, "data", "smpl_joint_regressor_female.npz")with np.load(regressor_path) as data:self.j_regressor_female = data['joint_regressor']if self.j_regressor_male is None:path = os.path.dirname(os.path.realpath(__file__))regressor_path = os.path.join(path, "data", "smpl_joint_regressor_male.npz")with np.load(regressor_path) as data:self.j_regressor_male = data['joint_regressor']if "female" in obj.name:j_regressor = self.j_regressor_femaleelse:j_regressor = self.j_regressor_male# Store current bone rotationsarmature = obj.parentbone_rotations = {}for pose_bone in armature.pose.bones:pose_bone.rotation_mode = 'AXIS_ANGLE'axis_angle = pose_bone.rotation_axis_anglebone_rotations[pose_bone.name] = (axis_angle[0], axis_angle[1], axis_angle[2], axis_angle[3])# Set model in default posefor bone in armature.pose.bones:bpy.ops.object.smpl_reset_poseshapes('EXEC_DEFAULT')bone.rotation_mode = 'AXIS_ANGLE'bone.rotation_axis_angle = (0, 0, 1, 0)# Reset corrective poseshapes if usedif context.window_manager.smpl_tool.smpl_corrective_poseshapes:bpy.ops.object.smpl_reset_poseshapes('EXEC_DEFAULT')# Get vertices with applied skin modifierdepsgraph = context.evaluated_depsgraph_get()object_eval = obj.evaluated_get(depsgraph)mesh_from_eval = object_eval.to_mesh()# Get Blender vertices as numpy matrixvertices_np = np.zeros((len(mesh_from_eval.vertices)*3), dtype=np.float)mesh_from_eval.vertices.foreach_get("co", vertices_np)vertices_matrix = np.reshape(vertices_np, (len(mesh_from_eval.vertices), 3))object_eval.to_mesh_clear() # Remove temporary mesh# Note: Current joint regressor uses 6890 vertices as input which is slow numpy operationjoint_locations = j_regressor @ vertices_matrix# Set new bone joint locationsbpy.context.view_layer.objects.active = armaturebpy.ops.object.mode_set(mode='EDIT')for index in range(smpl_joints):bone = armature.data.edit_bones[SMPL_JOINT_NAMES[index]]bone.head = (0.0, 0.0, 0.0)bone.tail = (0.0, 0.0, 0.1)bone_start = Vector(joint_locations[index])bone.translate(bone_start)bpy.ops.object.mode_set(mode='OBJECT')bpy.context.view_layer.objects.active = obj# Restore posefor pose_bone in armature.pose.bones:pose_bone.rotation_mode = 'AXIS_ANGLE'pose_bone.rotation_axis_angle = bone_rotations[pose_bone.name]# Restore corrective poseshapes if usedif context.window_manager.smpl_tool.smpl_corrective_poseshapes:bpy.ops.object.smpl_set_poseshapes('EXEC_DEFAULT')return {'FINISHED'}class SMPLSetPoseshapes(bpy.types.Operator):bl_idname = "object.smpl_set_poseshapes"bl_label = "Set Pose Shapes"bl_description = ("Sets corrective poseshapes for current pose")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh is active object and parent is armaturereturn ( ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE')) or (context.object.type == 'ARMATURE'))except: return False# https://github.com/gulvarol/surreal/blob/master/datageneration/main_part1.py# Computes rotation matrix through Rodrigues formula as in cv2.Rodriguesdef rodrigues_to_mat(self, rotvec):theta = np.linalg.norm(rotvec)r = (rotvec/theta).reshape(3, 1) if theta > 0. else rotveccost = 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)# https://github.com/gulvarol/surreal/blob/master/datageneration/main_part1.py# Calculate weights of pose corrective blend shapes# Input is pose of all 24 joints, output is weights for all joints except pelvis (23)def rodrigues_to_posecorrective_weight(self, pose):joints_posecorrective = smpl_jointsrod_rots = np.asarray(pose).reshape(joints_posecorrective, 3)mat_rots = [self.rodrigues_to_mat(rod_rot) for rod_rot in rod_rots]bshapes = np.concatenate([(mat_rot - np.eye(3)).ravel() for mat_rot in mat_rots[1:]])return(bshapes)def execute(self, context):obj = bpy.context.object# Get armature pose in rodrigues representationif obj.type == 'ARMATURE':armature = objobj = bpy.context.object.children[0]else:armature = obj.parentpose = [0.0] * (smpl_joints * 3)for index in range(smpl_joints):joint_name = SMPL_JOINT_NAMES[index]joint_pose = rodrigues_from_pose(armature, joint_name)pose[index*3 + 0] = joint_pose[0]pose[index*3 + 1] = joint_pose[1]pose[index*3 + 2] = joint_pose[2]# print("Current pose: " + str(pose))poseweights = self.rodrigues_to_posecorrective_weight(pose)# Set weights for pose corrective shape keysfor index, weight in enumerate(poseweights):obj.data.shape_keys.key_blocks["Pose%03d" % index].value = weight# Set checkbox without triggering update functioncontext.window_manager.smpl_tool["smpl_corrective_poseshapes"] = Truereturn {'FINISHED'}class SMPLResetPoseshapes(bpy.types.Operator):bl_idname = "object.smpl_reset_poseshapes"bl_label = "Reset"bl_description = ("Resets corrective poseshapes for current pose")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh is active object and parent is armaturereturn ( ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE')) or (context.object.type == 'ARMATURE'))except: return Falsedef execute(self, context):obj = bpy.context.objectif obj.type == 'ARMATURE':obj = bpy.context.object.children[0]for key_block in obj.data.shape_keys.key_blocks:if key_block.name.startswith("Pose"):key_block.value = 0.0return {'FINISHED'}def set_pose_from_rodrigues(armature, bone_name, rodrigues, rodrigues_reference=None, frame=1): # I wish frame=bpy.data.scenes[0].frame_current worked here, but it doesn'trod = Vector((rodrigues[0], rodrigues[1], rodrigues[2]))angle_rad = rod.lengthaxis = rod.normalized()pbone = armature.pose.bones[bone_name]pbone.rotation_mode = 'QUATERNION'quat = Quaternion(axis, angle_rad)if rodrigues_reference is None:pbone.rotation_quaternion = quatelse:# SMPL-X is adding the reference rodrigues rotation to the# relaxed hand rodrigues rotation, so we have to do the same here.# This means that pose values for relaxed hand model cannot be# interpreted as rotations in the local joint coordinate system of the relaxed hand.# https://github.com/vchoutas/smplx/blob/f4206853a4746139f61bdcf58571f2cea0cbebad/smplx/body_models.py#L1190# full_pose += self.pose_meanrod_reference = Vector((rodrigues_reference[0], rodrigues_reference[1], rodrigues_reference[2]))rod_result = rod + rod_referenceangle_rad_result = rod_result.lengthaxis_result = rod_result.normalized()quat_result = Quaternion(axis_result, angle_rad_result)pbone.rotation_quaternion = quat_resultpbone.keyframe_insert(data_path="rotation_quaternion", frame=frame)if bone_name == 'pelvis':pbone.keyframe_insert('location', frame=frame)return
class SMPLLoadPose(bpy.types.Operator):bl_idname = "object.smpl_load_pose"bl_label = "Load Pose"bl_description = ("Load SMPL pose thetas to console window")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh or armature is active objectreturn (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')except: return Falsedef execute(self, context):self.frame_number=5obj = bpy.context.objectif obj.type == 'MESH':armature = obj.parentelse:armature = objobj = armature.children[0]context.view_layer.objects.active = obj # mesh needs to be active object for recalculating joint locationsjoint_names = SMPL_JOINT_NAMESobj = bpy.context.objectnpz_path = r"C:\Program Files\Blender Foundation\Blender 4.0\1234.npz"npz_data = np.load(npz_path, allow_pickle=True)if 'joints_3d' not in npz_data:print('joints_3d not find')returndata = npz_data['joints_3d'].item()['data']body_pose = data.reshape(( 24, 3))logging.error("np.array(data):"+str(len(np.array(data))))# pose_index = max(0, min(self.frame_number, (len(np.array(data))))) # clamp the frame they give you from 0 and the max number of frames in this poses array# body_pose = np.array(data[pose_index]).reshape(len(joint_names), 3)# pose the entire bodyfor index in range(len(joint_names)):pose_rodrigues = body_pose[index]bone_name = joint_names[index]set_pose_from_rodrigues(armature, bone_name, pose_rodrigues, frame=bpy.data.scenes[0].frame_current)return {'FINISHED'}
class SMPLWritePose(bpy.types.Operator):bl_idname = "object.smpl_write_pose"bl_label = "Write Pose1"bl_description = ("Writes SMPL pose thetas to console window")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh or armature is active objectreturn (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')except: return Falsedef execute(self, context):obj = bpy.context.objectif obj.type == 'MESH':armature = obj.parentelse:armature = obj# Get armature pose in rodrigues representationpose = [0.0] * (len(SMPL_JOINT_NAMES) * 3)for index in range(len(SMPL_JOINT_NAMES)):joint_name = SMPL_JOINT_NAMES[index]joint_pose = rodrigues_from_pose(armature, joint_name)pose[index*3 + 0] = joint_pose[0]pose[index*3 + 1] = joint_pose[1]pose[index*3 + 2] = joint_pose[2]print("pose = " + str(pose))npz_file="1234.npz"np.savez_compressed(npz_file, joints_3d={"data": np.array([pose])})return {'FINISHED'}class SMPLResetPose(bpy.types.Operator):bl_idname = "object.smpl_reset_pose"bl_label = "Reset Pose"bl_description = ("Resets pose to default zero pose")bl_options = {'REGISTER', 'UNDO'}@classmethoddef poll(cls, context):try:# Enable button only if mesh is active objectreturn ( ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE')) or (context.object.type == 'ARMATURE'))except: return Falsedef execute(self, context):obj = bpy.context.objectif obj.type == 'MESH':armature = obj.parentelse:armature = objfor bone in armature.pose.bones:bone.rotation_mode = 'AXIS_ANGLE'bone.rotation_axis_angle = (0, 0, 1, 0)# Reset corrective pose shapesbpy.ops.object.smpl_reset_poseshapes('EXEC_DEFAULT')return {'FINISHED'}class SMPLExportUnityFBX(bpy.types.Operator, ExportHelper):bl_idname = "object.smpl_export_unity_fbx"bl_label = "Export Unity FBX"bl_description = ("Export skinned mesh to Unity in FBX format")bl_options = {'REGISTER', 'UNDO'}# ExportHelper mixin class uses thisfilename_ext = ".fbx"@classmethoddef poll(cls, context):try:# Enable button only if mesh is active objectreturn (context.object.type == 'MESH')except: return Falsedef execute(self, context):obj = bpy.context.objectexport_shape_keys = context.window_manager.smpl_tool.smpl_export_setting_shape_keysarmature_original = obj.parentskinned_mesh_original = obj# Operate on temporary copy of skinned mesh and armaturebpy.ops.object.select_all(action='DESELECT')skinned_mesh_original.select_set(True)armature_original.select_set(True)bpy.context.view_layer.objects.active = skinned_mesh_originalbpy.ops.object.duplicate()skinned_mesh = bpy.context.objectarmature = skinned_mesh.parent# Reset posebpy.ops.object.smpl_reset_pose('EXEC_DEFAULT')if export_shape_keys != 'SHAPE_POSE':# Remove pose corrective shape keysprint('Removing pose corrective shape keys')num_shape_keys = len(skinned_mesh.data.shape_keys.key_blocks.keys())current_shape_key_index = 0for index in range(0, num_shape_keys):bpy.context.object.active_shape_key_index = current_shape_key_indexif bpy.context.object.active_shape_key is not None:if bpy.context.object.active_shape_key.name.startswith('Pose'):bpy.ops.object.shape_key_remove(all=False)else:current_shape_key_index = current_shape_key_index + 1 if export_shape_keys == 'NONE':# Bake and remove shape keysprint("Baking shape and removing shape keys for shape")# Create shape mix for current shapebpy.ops.object.shape_key_add(from_mix=True)num_shape_keys = len(skinned_mesh.data.shape_keys.key_blocks.keys())# Remove all shape keys except newly added onebpy.context.object.active_shape_key_index = 0for count in range(0, num_shape_keys):bpy.ops.object.shape_key_remove(all=False)# Model (skeleton and skinned mesh) needs to have rotation of (90, 0, 0) when exporting so that it will have rotation (0, 0, 0) when imported into Unitybpy.ops.object.mode_set(mode='OBJECT')bpy.ops.object.select_all(action='DESELECT')skinned_mesh.select_set(True)skinned_mesh.rotation_euler = (radians(-90), 0, 0)bpy.context.view_layer.objects.active = skinned_meshbpy.ops.object.transform_apply(rotation = True)skinned_mesh.rotation_euler = (radians(90), 0, 0)skinned_mesh.select_set(False)armature.select_set(True)armature.rotation_euler = (radians(-90), 0, 0)bpy.context.view_layer.objects.active = armaturebpy.ops.object.transform_apply(rotation = True)armature.rotation_euler = (radians(90), 0, 0)# Select armature and skinned mesh for exportskinned_mesh.select_set(True)# Rename armature and skinned mesh to not contain Blender copy suffixif "female" in skinned_mesh.name:gender = "female"else:gender = "male"target_mesh_name = "SMPL-mesh-%s" % gendertarget_armature_name = "SMPL-%s" % genderif target_mesh_name in bpy.data.objects:bpy.data.objects[target_mesh_name].name = "SMPL-temp-mesh"skinned_mesh.name = target_mesh_nameif target_armature_name in bpy.data.objects:bpy.data.objects[target_armature_name].name = "SMPL-temp-armature"armature.name = target_armature_namebpy.ops.export_scene.fbx(filepath=self.filepath, use_selection=True, apply_scale_options="FBX_SCALE_ALL", add_leaf_bones=False)print("Exported: " + self.filepath)# Remove temporary copies of armature and skinned meshbpy.ops.object.select_all(action='DESELECT')skinned_mesh.select_set(True)armature.select_set(True)bpy.ops.object.delete()bpy.ops.object.select_all(action='DESELECT')skinned_mesh_original.select_set(True)bpy.context.view_layer.objects.active = skinned_mesh_originalif "SMPL-temp-mesh" in bpy.data.objects:bpy.data.objects["SMPL-temp-mesh"].name = target_mesh_nameif "SMPL-temp-armature" in bpy.data.objects:bpy.data.objects["SMPL-temp-armature"].name = target_armature_namereturn {'FINISHED'}class SMPL_PT_Model(bpy.types.Panel):bl_label = "SMPL Model"bl_category = "SMPL"bl_space_type = "VIEW_3D"bl_region_type = "UI"def draw(self, context):layout = self.layoutcol = layout.column(align=True)row = col.row(align=True)col.prop(context.window_manager.smpl_tool, "smpl_gender")col.operator("scene.smpl_add_gender", text="Add")col.separator()col.label(text="Texture:")row = col.row(align=True)split = row.split(factor=0.75, align=True)split.prop(context.window_manager.smpl_tool, "smpl_texture")split.operator("scene.smpl_set_texture", text="Set")class SMPL_PT_Shape(bpy.types.Panel):bl_label = "Shape"bl_category = "SMPL"bl_space_type = "VIEW_3D"bl_region_type = "UI"def draw(self, context):layout = self.layoutcol = layout.column(align=True)row = col.row(align=True)split = row.split(factor=0.75, align=True)split.operator("object.smpl_random_shapes")split.operator("object.smpl_reset_shapes")col.separator()col.operator("object.smpl_snap_ground_plane")col.separator()col.operator("object.smpl_update_joint_locations")class SMPL_PT_Pose(bpy.types.Panel):bl_label = "Pose"bl_category = "SMPL"bl_space_type = "VIEW_3D"bl_region_type = "UI"def draw(self, context):layout = self.layoutcol = layout.column(align=True)col.prop(context.window_manager.smpl_tool, "smpl_corrective_poseshapes")col.separator()col.operator("object.smpl_set_poseshapes")col.separator()col.operator("object.smpl_load_pose")col.separator()col.operator("object.smpl_write_pose")col.separator()class SMPL_PT_Export(bpy.types.Panel):bl_label = "Export"bl_category = "SMPL"bl_space_type = "VIEW_3D"bl_region_type = "UI"def draw(self, context):layout = self.layoutcol = layout.column(align=True)col.label(text="Shape Keys (Blend Shapes):")col.prop(context.window_manager.smpl_tool, "smpl_export_setting_shape_keys")col.separator()col.separator()col.operator("object.smpl_export_unity_fbx")col.separator()# export_button = col.operator("export_scene.obj", text="Export OBJ [m]", icon='EXPORT')
# export_button.global_scale = 1.0
# export_button.use_selection = True
# col.separator()row = col.row(align=True)row.operator("ed.undo", icon='LOOP_BACK')row.operator("ed.redo", icon='LOOP_FORWARDS')col.separator()(year, month, day) = bl_info["version"]col.label(text="Version: %s-%s-%s" % (year, month, day))classes = [PG_SMPLProperties,SMPLAddGender,SMPLSetTexture,SMPLRandomShapes,SMPLResetShapes,SMPLSnapGroundPlane,SMPLUpdateJointLocations,SMPLSetPoseshapes,SMPLResetPoseshapes,SMPLLoadPose,SMPLWritePose,SMPLResetPose,SMPLExportUnityFBX,SMPL_PT_Model,SMPL_PT_Shape,SMPL_PT_Pose,SMPL_PT_Export
]def register():from bpy.utils import register_classfor cls in classes:bpy.utils.register_class(cls)# Store properties under WindowManager (not Scene) so that they are not saved in .blend files and always show default values after loadingbpy.types.WindowManager.smpl_tool = PointerProperty(type=PG_SMPLProperties)def unregister():from bpy.utils import unregister_classfor cls in classes:bpy.utils.unregister_class(cls)del bpy.types.WindowManager.smpl_toolif __name__ == "__main__":register()