forked from bartvdbraak/blender
Advanced Retargeting option: If the end user armature is complex, on the level of Sintel/Mancandy rigs, the user is requested to mark Advanced Retargeting, and constraints will be semi automatically configured to retarget the animation and then Retargeting will bake and remove these constraints
This commit is contained in:
parent
fee7337249
commit
87e9c0ffaa
@ -21,6 +21,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from mathutils import *
|
from mathutils import *
|
||||||
from math import radians, acos
|
from math import radians, acos
|
||||||
|
from bl_operators import nla
|
||||||
import cProfile
|
import cProfile
|
||||||
|
|
||||||
|
|
||||||
@ -264,6 +265,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
|
|||||||
v = locDeriv[key][i]
|
v = locDeriv[key][i]
|
||||||
hipV = locDeriv[perfRoot][i]
|
hipV = locDeriv[perfRoot][i]
|
||||||
endV = locDeriv[perf_bones[key].bone.map][i]
|
endV = locDeriv[perf_bones[key].bone.map][i]
|
||||||
|
print(v.length,)
|
||||||
if (v.length < 0.1):
|
if (v.length < 0.1):
|
||||||
#this is a plant frame.
|
#this is a plant frame.
|
||||||
#lets see what the original hip delta is, and the corresponding
|
#lets see what the original hip delta is, and the corresponding
|
||||||
@ -284,6 +286,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
|
|||||||
stride_bone.name = "stride_bone"
|
stride_bone.name = "stride_bone"
|
||||||
print(stride_bone)
|
print(stride_bone)
|
||||||
stride_bone.location = Vector((0, 0, 0))
|
stride_bone.location = Vector((0, 0, 0))
|
||||||
|
print(linearAvg)
|
||||||
if linearAvg:
|
if linearAvg:
|
||||||
#determine the average change in scale needed
|
#determine the average change in scale needed
|
||||||
avg = sum(linearAvg) / len(linearAvg)
|
avg = sum(linearAvg) / len(linearAvg)
|
||||||
@ -295,6 +298,8 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame
|
|||||||
newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
|
newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
|
||||||
stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
|
stride_bone.location = enduser_obj_mat * (newTranslation - initialPos)
|
||||||
stride_bone.keyframe_insert("location")
|
stride_bone.keyframe_insert("location")
|
||||||
|
else:
|
||||||
|
stride_bone.keyframe_insert("location")
|
||||||
stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
|
stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
|
||||||
|
|
||||||
return stride_bone
|
return stride_bone
|
||||||
@ -439,10 +444,66 @@ def NLASystemInitialize(enduser_arm, context):#enduser_obj, name):
|
|||||||
anim_data.action = None
|
anim_data.action = None
|
||||||
|
|
||||||
|
|
||||||
|
def preAdvancedRetargeting(performer_obj, enduser_obj):
|
||||||
|
createDictionary(performer_obj.data, enduser_obj.data)
|
||||||
|
bones = enduser_obj.pose.bones
|
||||||
|
map_bones = [bone for bone in bones if bone.bone.reverseMap]
|
||||||
|
for bone in map_bones:
|
||||||
|
perf_bone = bone.bone.reverseMap[0].name
|
||||||
|
addLocalRot = False;
|
||||||
|
if bone.bone.use_connect or not bone.constraints:
|
||||||
|
locks = bone.lock_location
|
||||||
|
if not (locks[0] or locks[1] or locks[2]):
|
||||||
|
cons = bone.constraints.new('COPY_LOCATION')
|
||||||
|
cons.name = "retargetTemp"
|
||||||
|
cons.use_x = not locks[0]
|
||||||
|
cons.use_y = not locks[1]
|
||||||
|
cons.use_z = not locks[2]
|
||||||
|
cons.target = performer_obj
|
||||||
|
cons.subtarget = perf_bone
|
||||||
|
addLocalRot = True
|
||||||
|
|
||||||
|
|
||||||
|
cons2 = bone.constraints.new('COPY_ROTATION')
|
||||||
|
cons2.name = "retargetTemp"
|
||||||
|
locks = bone.lock_rotation
|
||||||
|
cons2.use_x = not locks[0]
|
||||||
|
cons2.use_y = not locks[1]
|
||||||
|
cons2.use_z = not locks[2]
|
||||||
|
cons2.target = performer_obj
|
||||||
|
cons2.subtarget = perf_bone
|
||||||
|
|
||||||
|
if addLocalRot:
|
||||||
|
for constraint in bone.constraints:
|
||||||
|
if constraint.type == 'COPY_ROTATION':
|
||||||
|
constraint.target_space = 'LOCAL'
|
||||||
|
constraint.owner_space = 'LOCAL_WITH_PARENT'
|
||||||
|
|
||||||
|
|
||||||
|
def prepareForBake(enduser_obj):
|
||||||
|
bones = enduser_obj.pose.bones
|
||||||
|
for bone in bones:
|
||||||
|
bone.bone.select = False
|
||||||
|
map_bones = [bone for bone in bones if bone.bone.reverseMap]
|
||||||
|
for bone in map_bones:
|
||||||
|
for cons in bone.constraints:
|
||||||
|
if "retargetTemp" in cons.name:
|
||||||
|
bone.bone.select = True
|
||||||
|
|
||||||
|
def cleanTempConstraints(enduser_obj):
|
||||||
|
bones = enduser_obj.pose.bones
|
||||||
|
map_bones = [bone for bone in bones if bone.bone.reverseMap]
|
||||||
|
for bone in map_bones:
|
||||||
|
for cons in bone.constraints:
|
||||||
|
if "retargetTemp" in cons.name:
|
||||||
|
bone.constraints.remove(cons)
|
||||||
|
|
||||||
#Main function that runs the retargeting sequence.
|
#Main function that runs the retargeting sequence.
|
||||||
|
#If advanced == True, we assume constraint's were already created
|
||||||
def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
|
def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
|
||||||
perf_arm = performer_obj.data
|
perf_arm = performer_obj.data
|
||||||
end_arm = enduser_obj.data
|
end_arm = enduser_obj.data
|
||||||
|
advanced = end_arm.advancedRetarget
|
||||||
|
|
||||||
try:
|
try:
|
||||||
enduser_obj.animation_data.action = bpy.data.actions.new("temp")
|
enduser_obj.animation_data.action = bpy.data.actions.new("temp")
|
||||||
@ -450,27 +511,33 @@ def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
|
|||||||
except:
|
except:
|
||||||
print("no need to create new action")
|
print("no need to create new action")
|
||||||
|
|
||||||
|
|
||||||
print("creating Dictionary")
|
print("creating Dictionary")
|
||||||
feetBones, root = createDictionary(perf_arm, end_arm)
|
feetBones, root = createDictionary(perf_arm, end_arm)
|
||||||
print("cleaning stuff up")
|
print("cleaning stuff up")
|
||||||
perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
|
perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
|
||||||
|
if not advanced:
|
||||||
turnOffIK(enduser_obj)
|
turnOffIK(enduser_obj)
|
||||||
print("Creating intermediate armature (for first pass)")
|
print("Creating intermediate armature (for first pass)")
|
||||||
inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene)
|
inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene)
|
||||||
print("First pass: retargeting from intermediate to end user")
|
print("First pass: retargeting from intermediate to end user")
|
||||||
|
|
||||||
|
|
||||||
retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
|
retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene)
|
||||||
|
else:
|
||||||
|
prepareForBake(enduser_obj)
|
||||||
|
print("Retargeting pose (Advanced Retarget)")
|
||||||
|
nla.bake(s_frame, e_frame, action=enduser_obj.animation_data.action, only_selected=True, do_pose=True, do_object=False)
|
||||||
name = performer_obj.animation_data.action.name
|
name = performer_obj.animation_data.action.name
|
||||||
enduser_obj.animation_data.action.name = "Base " + name
|
enduser_obj.animation_data.action.name = "Base " + name
|
||||||
print("Second pass: retargeting root translation and clean up")
|
print("Second pass: retargeting root translation and clean up")
|
||||||
stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
|
stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
|
||||||
|
if not advanced:
|
||||||
IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene)
|
IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene)
|
||||||
restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
|
restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone)
|
||||||
bpy.ops.object.mode_set(mode='OBJECT')
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
if not advanced:
|
||||||
bpy.ops.object.select_name(name=inter_obj.name, extend=False)
|
bpy.ops.object.select_name(name=inter_obj.name, extend=False)
|
||||||
bpy.ops.object.delete()
|
bpy.ops.object.delete()
|
||||||
|
else:
|
||||||
|
cleanTempConstraints(enduser_obj)
|
||||||
bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
|
bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
|
||||||
|
|
||||||
if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
|
if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
|
||||||
|
@ -140,9 +140,26 @@ class MocapNLATracks(bpy.types.PropertyGroup):
|
|||||||
|
|
||||||
bpy.utils.register_class(MocapNLATracks)
|
bpy.utils.register_class(MocapNLATracks)
|
||||||
|
|
||||||
|
|
||||||
|
def advancedRetargetToggle(self, context):
|
||||||
|
enduser_obj = context.active_object
|
||||||
|
performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
|
||||||
|
if enduser_obj is None or len(performer_obj) != 1:
|
||||||
|
print("Need active and selected armatures")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
performer_obj = performer_obj[0]
|
||||||
|
if self.advancedRetarget:
|
||||||
|
retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
|
||||||
|
else:
|
||||||
|
retarget.cleanTempConstraints(enduser_obj)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
|
bpy.types.Armature.stitch_settings = bpy.props.PointerProperty(type=AnimationStitchSettings)
|
||||||
bpy.types.Armature.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize)
|
bpy.types.Armature.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize)
|
||||||
bpy.types.Armature.mocapNLATracks = bpy.props.CollectionProperty(type=MocapNLATracks)
|
bpy.types.Armature.mocapNLATracks = bpy.props.CollectionProperty(type=MocapNLATracks)
|
||||||
|
bpy.types.Armature.advancedRetarget = bpy.props.BoolProperty(default=False, update=advancedRetargetToggle)
|
||||||
|
|
||||||
#Update function for IK functionality. Is called when IK prop checkboxes are toggled.
|
#Update function for IK functionality. Is called when IK prop checkboxes are toggled.
|
||||||
|
|
||||||
@ -281,6 +298,7 @@ class MocapPanel(bpy.types.Panel):
|
|||||||
mapRow.operator("mocap.savemapping", text='Save mapping')
|
mapRow.operator("mocap.savemapping", text='Save mapping')
|
||||||
mapRow.operator("mocap.loadmapping", text='Load mapping')
|
mapRow.operator("mocap.loadmapping", text='Load mapping')
|
||||||
self.layout.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
|
self.layout.prop(data=performer_obj.animation_data.action, property='name', text='Action Name')
|
||||||
|
self.layout.prop(enduser_arm, "advancedRetarget", text='Advanced Retarget')
|
||||||
self.layout.operator("mocap.retarget", text='RETARGET!')
|
self.layout.operator("mocap.retarget", text='RETARGET!')
|
||||||
|
|
||||||
|
|
||||||
@ -398,6 +416,35 @@ class OBJECT_OT_RetargetButton(bpy.types.Operator):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
#~ class OBJECT_OT_AdvancedRetargetButton(bpy.types.Operator):
|
||||||
|
#~ '''Prepare for advanced retargeting '''
|
||||||
|
#~ bl_idname = "mocap.preretarget"
|
||||||
|
#~ bl_label = "Prepares retarget of active action from Performer to Enduser"
|
||||||
|
|
||||||
|
#~ def execute(self, context):
|
||||||
|
#~ scene = context.scene
|
||||||
|
#~ s_frame = scene.frame_start
|
||||||
|
#~ e_frame = scene.frame_end
|
||||||
|
#~ enduser_obj = context.active_object
|
||||||
|
#~ performer_obj = [obj for obj in context.selected_objects if obj != enduser_obj]
|
||||||
|
#~ if enduser_obj is None or len(performer_obj) != 1:
|
||||||
|
#~ print("Need active and selected armatures")
|
||||||
|
#~ else:
|
||||||
|
#~ performer_obj = performer_obj[0]
|
||||||
|
#~ retarget.preAdvancedRetargeting(performer_obj, enduser_obj)
|
||||||
|
#~ return {"FINISHED"}
|
||||||
|
|
||||||
|
#~ @classmethod
|
||||||
|
#~ def poll(cls, context):
|
||||||
|
#~ if context.active_object:
|
||||||
|
#~ activeIsArmature = isinstance(context.active_object.data, bpy.types.Armature)
|
||||||
|
#~ performer_obj = [obj for obj in context.selected_objects if obj != context.active_object]
|
||||||
|
#~ if performer_obj:
|
||||||
|
#~ return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature)
|
||||||
|
#~ else:
|
||||||
|
#~ return False
|
||||||
|
|
||||||
|
|
||||||
class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
|
class OBJECT_OT_SaveMappingButton(bpy.types.Operator):
|
||||||
'''Save mapping to active armature (for future retargets) '''
|
'''Save mapping to active armature (for future retargets) '''
|
||||||
bl_idname = "mocap.savemapping"
|
bl_idname = "mocap.savemapping"
|
||||||
|
Loading…
Reference in New Issue
Block a user