Skip to content
Snippets Groups Projects
Commit 4f5d7a52 authored by Joachim Tesch's avatar Joachim Tesch
Browse files

Add new blend shape FBX export option to bake shape and expression and keep...

Add new blend shape FBX export option to bake shape and expression and keep pose corrective shape keys
parent f3f32bc6
No related branches found
No related tags found
No related merge requests found
......@@ -2,7 +2,7 @@
This add-on allows you to add [SMPL-X](https://smpl-x.is.tue.mpg.de) skinned meshes to your current Blender scene. Each imported SMPL-X mesh consists of a shape specific rig, as well as shape keys (blend shapes) for shape, expression and pose correctives.
+ Requirements: Blender 3.6+, tested with 4.0.2
+ Requirements: Blender 3.6+, tested with 4.2.3
+ Additional dependencies: None
+ Used SMPL-X models:
+ SMPL-X v1.1
......@@ -25,6 +25,7 @@ This add-on allows you to add [SMPL-X](https://smpl-x.is.tue.mpg.de) skinned mes
+ Over 3000 sample poses are available at https://agora.is.tuebingen.mpg.de/
+ Sign In > Download > Ground Truth Fittings > SMPL-X fits
+ Create animated body from animation .npz file (AMASS or SMPL-X)
+ Use "Locked Head" model version when working with animation files from AMASS
+ Alembic (.abc) export of animated body as animated vertex geometry cache
+ Keyframed pose correctives are always baked into the vertices on export
+ Alembic animation can be imported into other third-party tools
......@@ -35,9 +36,10 @@ This add-on allows you to add [SMPL-X](https://smpl-x.is.tue.mpg.de) skinned mes
+ Export to Unity or Unreal Engine
+ Imported FBX will import in Unity/Unreal without rotations and without scaling
+ Shape key export options:
+ Body shape and posecorrectives
+ Body shape without posecorrectives
+ None (bakes current body shape into mesh)
+ Body shape and pose correctives
+ Body shape without pose correctives
+ None (bakes current body shape into mesh, removes all pose correctives)
+ Pose correctives only (bakes current body shape and expression into mesh, keeps all shape keys for pose correctives)
## Installation
1. Register at https://smpl-x.is.tue.mpg.de and download the SMPL-X for Blender add-on. The ZIP release file will include the required SMPL-X model which is not included in the code repository.
......@@ -113,6 +115,11 @@ This add-on allows you to add [SMPL-X](https://smpl-x.is.tue.mpg.de) skinned mes
+ If body shape was created via shape key modification then the offset result from SnapToGroundPlane is stored. Armature must be in default pose for correct SnapToGroundPlane height offset calculation.
+ 20240418:
+ Fix rainbow texture dark areas at vertices 4146 and 6553
+ 20241127:
+ Save custom properties when using "Export FBX" so that add-on can also be used for reimported SMPL-X FBX files
+ Ensure valid shape key slider ranges so that add-on can also be used for reimported SMPL-X FBX files where initial range is [0, 1]
+ Add new blend shape export option to FBX export for exporting only pose corrective blend shapes
+ bakes current body shape and expression into mesh, keeps all pose correctives
## Contact
+ smplx-blender@tue.mpg.de
......@@ -21,7 +21,7 @@
bl_info = {
"name": "SMPL-X for Blender",
"author": "Joachim Tesch, Max Planck Institute for Intelligent Systems",
"version": (2024, 4, 8),
"version": (2024, 11, 27),
"blender": (3, 6, 0),
"location": "Viewport > Right panel",
"description": "SMPL-X for Blender",
......@@ -53,6 +53,7 @@ SMPLX_JOINT_NAMES = [
NUM_SMPLX_JOINTS = len(SMPLX_JOINT_NAMES)
NUM_SMPLX_BODYJOINTS = 21
NUM_SMPLX_HANDJOINTS = 15
SHAPEKEY_VALUE_RANGE=5
# End SMPL-X globals
def rodrigues_from_pose(armature, bone_name):
......@@ -108,6 +109,24 @@ def set_pose_from_rodrigues(armature, bone_name, rodrigues, rodrigues_reference=
"""
return
# Ensure that we have valid slider ranges, this needed for imported FBX files where the default range will be set to [0,1] on import
def smplx_ensure_valid_shapekey_slider_ranges(skinned_mesh):
update_slider_ranges = False
for key_name in ["Shape000", "Exp000", "Pose000"]:
if key_name in skinned_mesh.data.shape_keys.key_blocks:
key_block = skinned_mesh.data.shape_keys.key_blocks[key_name]
if (key_block.slider_min > -SHAPEKEY_VALUE_RANGE) or (key_block.slider_max < SHAPEKEY_VALUE_RANGE):
update_slider_ranges = True
break
if update_slider_ranges:
for index, key_block in enumerate(skinned_mesh.data.shape_keys.key_blocks):
if index == 0:
continue # skip Base shape key
key_block.slider_min = -SHAPEKEY_VALUE_RANGE
key_block.slider_max = SHAPEKEY_VALUE_RANGE
# Property groups for UI
class PG_SMPLXProperties(PropertyGroup):
......@@ -397,6 +416,7 @@ class SMPLXRandomShape(bpy.types.Operator):
def execute(self, context):
obj = bpy.context.object
bpy.ops.object.mode_set(mode='OBJECT')
smplx_ensure_valid_shapekey_slider_ranges(obj)
randomized_betas = 0
for key_block in obj.data.shape_keys.key_blocks:
if key_block.name.startswith("Shape"):
......@@ -452,6 +472,7 @@ class SMPLXRandomExpressionShape(bpy.types.Operator):
def execute(self, context):
obj = bpy.context.object
bpy.ops.object.mode_set(mode='OBJECT')
smplx_ensure_valid_shapekey_slider_ranges(obj)
for key_block in obj.data.shape_keys.key_blocks:
if key_block.name.startswith("Exp"):
key_block.value = np.random.uniform(-2, 2)
......@@ -646,6 +667,8 @@ class SMPLXSetPoseshapes(bpy.types.Operator):
else:
armature = obj.parent
smplx_ensure_valid_shapekey_slider_ranges(obj)
pose = [0.0] * (NUM_SMPLX_JOINTS * 3)
for index in range(NUM_SMPLX_JOINTS):
......@@ -1193,7 +1216,10 @@ class SMPLXExportFBX(bpy.types.Operator, ExportHelper):
export_shape_keys: EnumProperty(
name = "Blend Shapes",
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") ],
items = [ ("SHAPE_POSECORRECTIVES", "All: Shape + Posecorrectives", "Export shape keys for body shape and pose correctives"),
("SHAPE", "Reduced: Shape space only", "Export only shape keys for body shape"),
("POSECORRECTIVES", "Reduced: Posecorrectives only", "Bake shape and expression into mesh, export only shape keys for pose correctives"),
("NONE", "None: Apply shape space", "Do not export any shape keys, shape keys for body shape will be baked into mesh") ],
)
......@@ -1246,7 +1272,7 @@ class SMPLXExportFBX(bpy.types.Operator, ExportHelper):
# Reset pose
bpy.ops.object.smplx_reset_pose('EXEC_DEFAULT')
if self.export_shape_keys != 'SHAPE_POSE':
if ( (self.export_shape_keys == 'SHAPE') or (self.export_shape_keys == 'NONE') ):
# Remove pose corrective shape keys
print("Removing pose corrective shape keys")
num_shape_keys = len(skinned_mesh.data.shape_keys.key_blocks.keys())
......@@ -1265,6 +1291,11 @@ class SMPLXExportFBX(bpy.types.Operator, ExportHelper):
# Bake and remove shape keys
print("Baking shape and removing shape keys for shape")
# Zero out all pose corrective weights so that they do not contribute to baked shape
for key_block in skinned_mesh.data.shape_keys.key_blocks:
if key_block.name.startswith("Pose"):
key_block.value = 0.0
# 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())
......@@ -1274,6 +1305,39 @@ class SMPLXExportFBX(bpy.types.Operator, ExportHelper):
for count in range(0, num_shape_keys):
bpy.ops.object.shape_key_remove(all=False)
elif self.export_shape_keys == 'POSECORRECTIVES':
# Bake shape and expression into Base shape key
print("Baking shape and expression into Base shape key")
# Zero out all pose corrective weights so that they do not contribute to baked shape
for key_block in skinned_mesh.data.shape_keys.key_blocks:
if key_block.name.startswith("Pose"):
key_block.value = 0.0
# Create shape mix from current shape and expression
bpy.ops.object.shape_key_add(from_mix=True)
bpy.context.object.active_shape_key.name = "ShapeMix"
# Copy shape mix vertices intp Base shape key
bpy.context.object.active_shape_key_index = 0 # Select Base shape key
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.blend_from_shape(shape="ShapeMix", blend=1, add=False)
bpy.ops.object.mode_set(mode='OBJECT')
# Remove all shape and expression keys and shape mix
num_shape_keys = len(skinned_mesh.data.shape_keys.key_blocks.keys())
current_shape_key_index = 1
for _ in range(1, 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('Shape') or
bpy.context.object.active_shape_key.name.startswith('Exp')):
bpy.ops.object.shape_key_remove(all=False)
else:
current_shape_key_index = current_shape_key_index + 1
bpy.context.object.active_shape_key_index = 0
# 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')
......@@ -1329,7 +1393,14 @@ class SMPLXExportFBX(bpy.types.Operator, ExportHelper):
# Default FBX export settings export all animations. Since we duplicated the armature we have a copy of the animation and the original animation.
# We avoid export of both by only exporting the active animation for the armature (bake_anim_use_nla_strips=False, bake_anim_use_all_actions=False).
# Disable keyframe simplification to ensure that exported FBX animation properly matches up with exported Alembic cache.
bpy.ops.export_scene.fbx(filepath=self.filepath, use_selection=True, apply_scale_options="FBX_SCALE_ALL", add_leaf_bones=False, bake_anim_use_nla_strips=False, bake_anim_use_all_actions=False, bake_anim_simplify_factor=0)
bpy.ops.export_scene.fbx(filepath=self.filepath,
use_selection=True,
apply_scale_options="FBX_SCALE_ALL",
use_custom_props=True,
add_leaf_bones=False,
bake_anim_use_nla_strips=False,
bake_anim_use_all_actions=False,
bake_anim_simplify_factor=0)
print("Exported: " + self.filepath)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment