From 87e9c0ffaa5f8f64ccdea5c2ce74dfbd0edf0e43 Mon Sep 17 00:00:00 2001 From: Benjy Cook Date: Thu, 11 Aug 2011 13:47:49 +0000 Subject: [PATCH] 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 --- release/scripts/modules/retarget.py | 89 +++++++++++++++++++++++++---- release/scripts/startup/ui_mocap.py | 51 ++++++++++++++++- 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/release/scripts/modules/retarget.py b/release/scripts/modules/retarget.py index 9415553d4ff..88a5c3a620a 100644 --- a/release/scripts/modules/retarget.py +++ b/release/scripts/modules/retarget.py @@ -21,6 +21,7 @@ import bpy from mathutils import * from math import radians, acos +from bl_operators import nla import cProfile @@ -264,6 +265,7 @@ def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame v = locDeriv[key][i] hipV = locDeriv[perfRoot][i] endV = locDeriv[perf_bones[key].bone.map][i] + print(v.length,) if (v.length < 0.1): #this is a plant frame. #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" print(stride_bone) stride_bone.location = Vector((0, 0, 0)) + print(linearAvg) if linearAvg: #determine the average change in scale needed 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) stride_bone.location = enduser_obj_mat * (newTranslation - initialPos) stride_bone.keyframe_insert("location") + else: + stride_bone.keyframe_insert("location") stride_bone.animation_data.action.name = ("Stride Bone " + action_name) return stride_bone @@ -439,10 +444,66 @@ def NLASystemInitialize(enduser_arm, context):#enduser_obj, name): 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. +#If advanced == True, we assume constraint's were already created def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame): perf_arm = performer_obj.data end_arm = enduser_obj.data + advanced = end_arm.advancedRetarget try: 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: print("no need to create new action") - print("creating Dictionary") feetBones, root = createDictionary(perf_arm, end_arm) print("cleaning stuff up") perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj) - turnOffIK(enduser_obj) - print("Creating intermediate armature (for first pass)") - inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene) - print("First pass: retargeting from intermediate to end user") - - - retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene) + if not advanced: + turnOffIK(enduser_obj) + print("Creating intermediate armature (for first pass)") + inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene) + print("First pass: retargeting from intermediate to end user") + 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 enduser_obj.animation_data.action.name = "Base " + name 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) - IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene) + if not advanced: + IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene) restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone) bpy.ops.object.mode_set(mode='OBJECT') - bpy.ops.object.select_name(name=inter_obj.name, extend=False) - bpy.ops.object.delete() + if not advanced: + bpy.ops.object.select_name(name=inter_obj.name, extend=False) + bpy.ops.object.delete() + else: + cleanTempConstraints(enduser_obj) bpy.ops.object.select_name(name=enduser_obj.name, extend=False) if not name in [tracks.name for tracks in end_arm.mocapNLATracks]: diff --git a/release/scripts/startup/ui_mocap.py b/release/scripts/startup/ui_mocap.py index 0788366547e..06d0bb0b415 100644 --- a/release/scripts/startup/ui_mocap.py +++ b/release/scripts/startup/ui_mocap.py @@ -140,9 +140,26 @@ class MocapNLATracks(bpy.types.PropertyGroup): 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.active_mocap = bpy.props.StringProperty(update=retarget.NLASystemInitialize) 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. @@ -189,7 +206,7 @@ def toggleIKBone(self, context): for bone in cnstrn_bone.parent_recursive: if not bone.is_in_ik_chain: bone.IKRetarget = False - + class MocapMapping(bpy.types.PropertyGroup): name = bpy.props.StringProperty() @@ -281,6 +298,7 @@ class MocapPanel(bpy.types.Panel): mapRow.operator("mocap.savemapping", text='Save 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(enduser_arm, "advancedRetarget", text='Advanced Retarget') self.layout.operator("mocap.retarget", text='RETARGET!') @@ -396,6 +414,35 @@ class OBJECT_OT_RetargetButton(bpy.types.Operator): return activeIsArmature and isinstance(performer_obj[0].data, bpy.types.Armature) else: 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): @@ -715,7 +762,7 @@ class OBJECT_OT_AnimationStitchingButton(bpy.types.Operator): stitch_settings = context.active_object.data.stitch_settings return (stitch_settings.first_action and stitch_settings.second_action) return False - + def register(): bpy.utils.register_module(__name__)