目录
文件拖拽导入
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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh or armature is active object
return ((context.object.type == 'MESH') or (context.object.type == 'ARMATURE'))
except: return False
def execute(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
obj = bpy.context.object
if obj.type == 'ARMATURE':
armature = obj
obj = bpy.context.object.children[0]
else:
armature = obj.parent
# Get vertices with applied skin modifier in object coordinates
depsgraph = context.evaluated_depsgraph_get()
object_eval = obj.evaluated_get(depsgraph)
mesh_from_eval = object_eval.to_mesh()
# Get vertices in world coordinates
matrix_world = obj.matrix_world
vertices_world = [matrix_world @ vertex.co for vertex in mesh_from_eval.vertices]
z_min = (min(vertices_world, key=lambda item: item.z)).z
object_eval.to_mesh_clear() # Remove temporary mesh
# Translate armature edit bones
context.view_layer.objects.active = armature
bpy.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 translation
bpy.ops.object.mode_set(mode='OBJECT')
context.view_layer.objects.active = obj
obj.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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh or armature is active object
return (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')
except: return False
def execute(self, context):
obj = bpy.context.object
if obj.type == 'MESH':
armature = obj.parent
else:
armature = obj
# Get armature pose in rodrigues representation
pose = [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 logging
bl_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 pickle
from 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 globals
def rodrigues_from_pose(armature, bone_name):
# Ensure that rotation mode is AXIS_ANGLE so the we get a correct readout of current pose
armature.pose.bones[bone_name].rotation_mode = 'AXIS_ANGLE'
axis_angle = armature.pose.bones[bone_name].rotation_axis_angle
angle = axis_angle[0]
rodrigues = Vector((axis_angle[1], axis_angle[2], axis_angle[3]))
rodrigues.normalize()
rodrigues = rodrigues * angle
return rodrigues
def 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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if in Object Mode
if (context.active_object is None) or (context.active_object.mode == 'OBJECT'):
return True
else:
return False
except: return False
def execute(self, context):
gender = context.window_manager.smpl_tool.smpl_gender
print("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-" + gender
bpy.ops.wm.append(filename=object_name, directory=str(objects_path))
# Select imported mesh
object_name = context.selected_objects[0].name
bpy.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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if in active object is mesh
if (context.object.type == 'MESH'):
return True
else:
return False
except: return False
def execute(self, context):
texture = context.window_manager.smpl_tool.smpl_texture
print("Setting texture: " + texture)
obj = bpy.context.object
if (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.links
nodes = mat.node_tree.nodes
# Find texture node
node_texture = None
for node in nodes:
if node.type == 'TEX_IMAGE':
node_texture = node
break
# Find shader node
node_shader = None
for node in nodes:
if node.type.startswith('BSDF'):
node_shader = node
break
if texture == 'NONE':
# Unlink texture node
if 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_value
else:
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 linked
if 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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object
return context.object.type == 'MESH'
except: return False
def execute(self, context):
obj = bpy.context.object
bpy.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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object
return context.object.type == 'MESH'
except: return False
def execute(self, context):
obj = bpy.context.object
bpy.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.0
bpy.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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh or armature is active object
return ((context.object.type == 'MESH') or (context.object.type == 'ARMATURE'))
except: return False
def execute(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
obj = bpy.context.object
if obj.type == 'ARMATURE':
armature = obj
obj = bpy.context.object.children[0]
else:
armature = obj.parent
# Get vertices with applied skin modifier in object coordinates
depsgraph = context.evaluated_depsgraph_get()
object_eval = obj.evaluated_get(depsgraph)
mesh_from_eval = object_eval.to_mesh()
# Get vertices in world coordinates
matrix_world = obj.matrix_world
vertices_world = [matrix_world @ vertex.co for vertex in mesh_from_eval.vertices]
z_min = (min(vertices_world, key=lambda item: item.z)).z
object_eval.to_mesh_clear() # Remove temporary mesh
# Translate armature edit bones
context.view_layer.objects.active = armature
bpy.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 translation
bpy.ops.object.mode_set(mode='OBJECT')
context.view_layer.objects.active = obj
obj.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 = None
j_regressor_female = None
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object
return ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE'))
except: return False
def execute(self, context):
obj = bpy.context.object
bpy.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_female
else:
j_regressor = self.j_regressor_male
# Store current bone rotations
armature = obj.parent
bone_rotations = {}
for pose_bone in armature.pose.bones:
pose_bone.rotation_mode = 'AXIS_ANGLE'
axis_angle = pose_bone.rotation_axis_angle
bone_rotations[pose_bone.name] = (axis_angle[0], axis_angle[1], axis_angle[2], axis_angle[3])
# Set model in default pose
for 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 used
if context.window_manager.smpl_tool.smpl_corrective_poseshapes:
bpy.ops.object.smpl_reset_poseshapes('EXEC_DEFAULT')
# Get vertices with applied skin modifier
depsgraph = context.evaluated_depsgraph_get()
object_eval = obj.evaluated_get(depsgraph)
mesh_from_eval = object_eval.to_mesh()
# Get Blender vertices as numpy matrix
vertices_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 operation
joint_locations = j_regressor @ vertices_matrix
# Set new bone joint locations
bpy.context.view_layer.objects.active = armature
bpy.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 pose
for 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 used
if 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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object and parent is armature
return ( ((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.Rodrigues
def rodrigues_to_mat(self, 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)
# 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_joints
rod_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 representation
if obj.type == 'ARMATURE':
armature = obj
obj = bpy.context.object.children[0]
else:
armature = obj.parent
pose = [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 keys
for index, weight in enumerate(poseweights):
obj.data.shape_keys.key_blocks["Pose%03d" % index].value = weight
# Set checkbox without triggering update function
context.window_manager.smpl_tool["smpl_corrective_poseshapes"] = True
return {'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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object and parent is armature
return ( ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE')) or (context.object.type == 'ARMATURE'))
except: return False
def execute(self, context):
obj = bpy.context.object
if 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.0
return {'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't
rod = Vector((rodrigues[0], rodrigues[1], rodrigues[2]))
angle_rad = rod.length
axis = 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 = quat
else:
# 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_mean
rod_reference = Vector((rodrigues_reference[0], rodrigues_reference[1], rodrigues_reference[2]))
rod_result = rod + rod_reference
angle_rad_result = rod_result.length
axis_result = rod_result.normalized()
quat_result = Quaternion(axis_result, angle_rad_result)
pbone.rotation_quaternion = quat_result
pbone.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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh or armature is active object
return (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')
except: return False
def execute(self, context):
self.frame_number=5
obj = bpy.context.object
if obj.type == 'MESH':
armature = obj.parent
else:
armature = obj
obj = armature.children[0]
context.view_layer.objects.active = obj # mesh needs to be active object for recalculating joint locations
joint_names = SMPL_JOINT_NAMES
obj = bpy.context.object
npz_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')
return
data = 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 body
for 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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh or armature is active object
return (context.object.type == 'MESH') or (context.object.type == 'ARMATURE')
except: return False
def execute(self, context):
obj = bpy.context.object
if obj.type == 'MESH':
armature = obj.parent
else:
armature = obj
# Get armature pose in rodrigues representation
pose = [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'}
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object
return ( ((context.object.type == 'MESH') and (context.object.parent.type == 'ARMATURE')) or (context.object.type == 'ARMATURE'))
except: return False
def execute(self, context):
obj = bpy.context.object
if obj.type == 'MESH':
armature = obj.parent
else:
armature = obj
for bone in armature.pose.bones:
bone.rotation_mode = 'AXIS_ANGLE'
bone.rotation_axis_angle = (0, 0, 1, 0)
# Reset corrective pose shapes
bpy.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 this
filename_ext = ".fbx"
@classmethod
def poll(cls, context):
try:
# Enable button only if mesh is active object
return (context.object.type == 'MESH')
except: return False
def execute(self, context):
obj = bpy.context.object
export_shape_keys = context.window_manager.smpl_tool.smpl_export_setting_shape_keys
armature_original = obj.parent
skinned_mesh_original = obj
# Operate on temporary copy of skinned mesh and armature
bpy.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_original
bpy.ops.object.duplicate()
skinned_mesh = bpy.context.object
armature = skinned_mesh.parent
# Reset pose
bpy.ops.object.smpl_reset_pose('EXEC_DEFAULT')
if export_shape_keys != 'SHAPE_POSE':
# Remove pose corrective shape keys
print('Removing pose corrective shape keys')
num_shape_keys = len(skinned_mesh.data.shape_keys.key_blocks.keys())
current_shape_key_index = 0
for index in range(0, num_shape_keys):
bpy.context.object.active_shape_key_index = current_shape_key_index
if 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 keys
print("Baking shape and removing shape keys for shape")
# Create shape mix for current shape
bpy.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 one
bpy.context.object.active_shape_key_index = 0
for 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 Unity
bpy.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_mesh
bpy.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 = armature
bpy.ops.object.transform_apply(rotation = True)
armature.rotation_euler = (radians(90), 0, 0)
# Select armature and skinned mesh for export
skinned_mesh.select_set(True)
# Rename armature and skinned mesh to not contain Blender copy suffix
if "female" in skinned_mesh.name:
gender = "female"
else:
gender = "male"
target_mesh_name = "SMPL-mesh-%s" % gender
target_armature_name = "SMPL-%s" % gender
if target_mesh_name in bpy.data.objects:
bpy.data.objects[target_mesh_name].name = "SMPL-temp-mesh"
skinned_mesh.name = target_mesh_name
if target_armature_name in bpy.data.objects:
bpy.data.objects[target_armature_name].name = "SMPL-temp-armature"
armature.name = target_armature_name
bpy.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 mesh
bpy.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_original
if "SMPL-temp-mesh" in bpy.data.objects:
bpy.data.objects["SMPL-temp-mesh"].name = target_mesh_name
if "SMPL-temp-armature" in bpy.data.objects:
bpy.data.objects["SMPL-temp-armature"].name = target_armature_name
return {'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.layout
col = 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.layout
col = 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.layout
col = 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.layout
col = 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_class
for 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 loading
bpy.types.WindowManager.smpl_tool = PointerProperty(type=PG_SMPLProperties)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
bpy.utils.unregister_class(cls)
del bpy.types.WindowManager.smpl_tool
if __name__ == "__main__":
register()