Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrendon Murphy <meta.androcto1@gmail.com>2011-08-24 15:45:59 +0400
committerBrendon Murphy <meta.androcto1@gmail.com>2011-08-24 15:45:59 +0400
commit8f5fe7f748f924d504f708b89a4f143de1b636cc (patch)
tree49dbf70f627de7e1ac5358c05a8cbe41534e2cc7 /mocap/retarget.py
parentcf3881371a5c64d56f352cf09395f93072889a7c (diff)
committing Motion Capture Add-on - Final deliverable for GSoC project 2011
thanks to Benjy Cook well done!
Diffstat (limited to 'mocap/retarget.py')
-rw-r--r--mocap/retarget.py557
1 files changed, 557 insertions, 0 deletions
diff --git a/mocap/retarget.py b/mocap/retarget.py
new file mode 100644
index 00000000..6e669434
--- /dev/null
+++ b/mocap/retarget.py
@@ -0,0 +1,557 @@
+# ##### 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 #####
+
+# <pep8 compliant>
+
+import bpy
+from mathutils import *
+from math import radians, acos, pi
+from bl_operators import nla
+
+
+def hasIKConstraint(pose_bone):
+ #utility function / predicate, returns True if given bone has IK constraint
+ ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
+ if ik:
+ return ik[0]
+ else:
+ return False
+
+
+def createDictionary(perf_arm, end_arm):
+ # clear any old data
+ for end_bone in end_arm.bones:
+ for mapping in end_bone.reverseMap:
+ end_bone.reverseMap.remove(0)
+
+ for perf_bone in perf_arm.bones:
+ #find its match and add perf_bone to the match's mapping
+ if perf_bone.map:
+ end_bone = end_arm.bones[perf_bone.map]
+ newMap = end_bone.reverseMap.add()
+ newMap.name = perf_bone.name
+ end_bone.foot = perf_bone.foot
+
+ #root is the root of the enduser
+ root = end_arm.bones[0].name
+ feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
+ return feetBones, root
+
+
+def loadMapping(perf_arm, end_arm):
+ for end_bone in end_arm.bones:
+ #find its match and add perf_bone to the match's mapping
+ if end_bone.reverseMap:
+ for perf_bone in end_bone.reverseMap:
+ perf_arm.bones[perf_bone.name].map = end_bone.name
+
+#creation of intermediate armature
+# the intermediate armature has the hiearchy of the end user,
+# does not have rotation inheritence
+# and bone roll is identical to the performer
+# its purpose is to copy over the rotations
+# easily while concentrating on the hierarchy changes
+
+
+def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step):
+ #creates and keyframes an empty with its location
+ #the original position of the tail bone
+ #useful for storing the important data in the original motion
+ #i.e. using this empty to IK the chain to that pos / DEBUG
+
+ #Simple 1to1 retarget of a bone
+ def singleBoneRetarget(inter_bone, perf_bone):
+ perf_world_rotation = perf_bone.matrix
+ inter_world_base_rotation = inter_bone.bone.matrix_local
+ inter_world_base_inv = inter_world_base_rotation.inverted()
+ bake_matrix = (inter_world_base_inv.to_3x3() * perf_world_rotation.to_3x3())
+ return bake_matrix.to_4x4()
+
+ #uses 1to1 and interpolation/averaging to match many to 1 retarget
+ def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
+ retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
+ lerp_matrix = Matrix()
+ for i in range(len(retarget_matrices) - 1):
+ first_mat = retarget_matrices[i]
+ next_mat = retarget_matrices[i + 1]
+ lerp_matrix = first_mat.lerp(next_mat, 0.5)
+ return lerp_matrix
+
+ #determines the type of hierachy change needed and calls the
+ #right function
+ def retargetPerfToInter(inter_bone):
+ if inter_bone.bone.reverseMap:
+ perf_bone_name = inter_bone.bone.reverseMap
+ # 1 to many not supported yet
+ # then its either a many to 1 or 1 to 1
+ if len(perf_bone_name) > 1:
+ performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
+ #we need to map several performance bone to a single
+ inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
+ else:
+ perf_bone = performer_bones[perf_bone_name[0].name]
+ inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
+ #Some bones have incorrect roll on the source armature, and need to be marked for fixing
+ if inter_bone.bone.twistFix:
+ inter_bone.matrix_basis *= Matrix.Rotation(radians(180), 4, "Y")
+ rot_mode = inter_bone.rotation_mode
+ if rot_mode == "QUATERNION":
+ inter_bone.keyframe_insert("rotation_quaternion")
+ elif rot_mode == "AXIS_ANGLE":
+ inter_bone.keyframe_insert("rotation_axis_angle")
+ else:
+ inter_bone.keyframe_insert("rotation_euler")
+
+ #creates the intermediate armature object
+ inter_obj = enduser_obj.copy()
+ inter_obj.data = inter_obj.data.copy() # duplicate data
+ bpy.context.scene.objects.link(inter_obj)
+ inter_obj.name = "intermediate"
+ bpy.context.scene.objects.active = inter_obj
+ bpy.ops.object.mode_set(mode='EDIT')
+ #add some temporary connecting bones in case end user bones are not connected to their parents
+ rollDict = {}
+ print("creating temp bones")
+ for bone in inter_obj.data.edit_bones:
+ if not bone.use_connect and bone.parent:
+ if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
+ newBone = inter_obj.data.edit_bones.new("Temp")
+ newBone.head = bone.parent.tail
+ newBone.tail = bone.head
+ newBone.parent = bone.parent
+ bone.parent = newBone
+ bone.use_connect = True
+ newBone.use_connect = True
+ rollDict[bone.name] = bone.roll
+ bone.roll = 0
+ #resets roll
+ print("retargeting to intermediate")
+ bpy.ops.object.mode_set(mode="OBJECT")
+ inter_obj.data.name = "inter_arm"
+ inter_arm = inter_obj.data
+ performer_bones = performer_obj.pose.bones
+ inter_bones = inter_obj.pose.bones
+ #clears inheritance
+ for inter_bone in inter_bones:
+ if inter_bone.bone.reverseMap:
+ inter_bone.bone.use_inherit_rotation = False
+ else:
+ inter_bone.bone.use_inherit_rotation = True
+
+ for t in range(s_frame, e_frame, step):
+ if (t - s_frame) % 10 == 0:
+ print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
+ scene.frame_set(t)
+ for bone in inter_bones:
+ retargetPerfToInter(bone)
+
+ return inter_obj
+
+# this procedure copies the rotations over from the intermediate
+# armature to the end user one.
+# As the hierarchies are 1 to 1, this is a simple matter of
+# copying the rotation, while keeping in mind bone roll, parenting, etc.
+# TODO: Control Bones: If a certain bone is constrained in a way
+# that its rotation is determined by another (a control bone)
+# We should determine the right pos of the control bone.
+# Scale: ? Should work but needs testing.
+
+
+def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step):
+ inter_bones = inter_obj.pose.bones
+ end_bones = enduser_obj.pose.bones
+
+ #Basic "visual baking" function, for transfering rotations from intermediate to end user
+ def bakeTransform(end_bone):
+ src_bone = inter_bones[end_bone.name]
+ trg_bone = end_bone
+ bake_matrix = src_bone.matrix
+ rest_matrix = trg_bone.bone.matrix_local
+
+ if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
+ srcParent = src_bone.parent
+ if "Temp" in srcParent.name:
+ srcParent = srcParent.parent
+ parent_mat = srcParent.matrix
+ parent_rest = trg_bone.parent.bone.matrix_local
+ parent_rest_inv = parent_rest.inverted()
+ parent_mat_inv = parent_mat.inverted()
+ bake_matrix = parent_mat_inv * bake_matrix
+ rest_matrix = parent_rest_inv * rest_matrix
+
+ rest_matrix_inv = rest_matrix.inverted()
+ bake_matrix = rest_matrix_inv * bake_matrix
+ end_bone.matrix_basis = bake_matrix
+ rot_mode = end_bone.rotation_mode
+ if rot_mode == "QUATERNION":
+ end_bone.keyframe_insert("rotation_quaternion")
+ elif rot_mode == "AXIS_ANGLE":
+ end_bone.keyframe_insert("rotation_axis_angle")
+ else:
+ end_bone.keyframe_insert("rotation_euler")
+ if not end_bone.bone.use_connect:
+ end_bone.keyframe_insert("location")
+
+ for bone in end_bone.children:
+ bakeTransform(bone)
+
+ for t in range(s_frame, e_frame, step):
+ if (t - s_frame) % 10 == 0:
+ print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
+ scene.frame_set(t)
+ end_bone = end_bones[root]
+ end_bone.location = Vector((0, 0, 0))
+ end_bone.keyframe_insert("location")
+ bakeTransform(end_bone)
+
+#recieves the performer feet bones as a variable
+# by "feet" I mean those bones that have plants
+# (they don't move, despite root moving) somewhere in the animation.
+
+
+def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
+
+ perf_bones = performer_obj.pose.bones
+ end_bones = enduser_obj.pose.bones
+
+ perfRoot = perf_bones[0].name
+ endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
+ locDictKeys = perfFeet + endFeet + [perfRoot]
+
+ def tailLoc(bone):
+ return bone.center + (bone.vector / 2)
+
+ #Step 1 - we create a dict that contains these keys:
+ #(Performer) Hips, Feet
+ #(End user) Feet
+ # where the values are their world position on each frame in range (s,e)
+
+ locDict = {}
+ for key in locDictKeys:
+ locDict[key] = []
+
+ for t in range(scene.frame_start, scene.frame_end):
+ scene.frame_set(t)
+ for bone in perfFeet:
+ locDict[bone].append(tailLoc(perf_bones[bone]))
+ locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
+ for bone in endFeet:
+ locDict[bone].append(tailLoc(end_bones[bone]))
+
+ # now we take our locDict and analyze it.
+ # we need to derive all chains
+
+ def locDeriv(key, t):
+ graph = locDict[key]
+ return graph[t + 1] - graph[t]
+
+ # now find the plant frames, where perfFeet don't move much
+
+ linearAvg = []
+
+ for key in perfFeet:
+ for i in range(len(locDict[key]) - 1):
+ v = locDeriv(key, i)
+ if (v.length < 0.1):
+ hipV = locDeriv(perfRoot, i)
+ endV = locDeriv(perf_bones[key].bone.map, i)
+ #this is a plant frame.
+ #lets see what the original hip delta is, and the corresponding
+ #end bone's delta
+ if endV.length != 0:
+ linearAvg.append(hipV.length / endV.length)
+
+ action_name = performer_obj.animation_data.action.name
+ #is there a stride_bone?
+ if "stride_bone" in bpy.data.objects:
+ stride_action = bpy.data.actions.new("Stride Bone " + action_name)
+ stride_action.use_fake_user = True
+ stride_bone = enduser_obj.parent
+ stride_bone.animation_data.action = stride_action
+ else:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.add()
+ stride_bone = bpy.context.active_object
+ stride_bone.name = "stride_bone"
+ stride_bone.location = enduser_obj_mat.to_translation()
+ if linearAvg:
+ #determine the average change in scale needed
+ avg = sum(linearAvg) / len(linearAvg)
+ scene.frame_set(s_frame)
+ initialPos = (tailLoc(perf_bones[perfRoot]) / avg)
+ for t in range(s_frame, e_frame):
+ scene.frame_set(t)
+ #calculate the new position, by dividing by the found ratio between performer and enduser
+ 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
+
+
+def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
+ bpy.ops.object.select_name(name=enduser_obj.name, extend=False)
+ end_bones = enduser_obj.pose.bones
+ for pose_bone in end_bones:
+ ik_constraint = hasIKConstraint(pose_bone)
+ if ik_constraint:
+ target_is_bone = False
+ # set constraint target to corresponding empty if targetless,
+ # if not, keyframe current target to corresponding empty
+ perf_bone = pose_bone.bone.reverseMap[-1].name
+ bpy.ops.object.mode_set(mode='EDIT')
+ orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if not ik_constraint.target:
+ ik_constraint.target = enduser_obj
+ ik_constraint.subtarget = pose_bone.name + "IK"
+ target = orgLocTrg
+
+ # There is a target now
+ if ik_constraint.subtarget:
+ target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
+ target.bone.use_local_location = False
+ target_is_bone = True
+ else:
+ target = ik_constraint.target
+
+ # bake the correct locations for the ik target bones
+ for t in range(s_frame, e_frame, step):
+ scene.frame_set(t)
+ if target_is_bone:
+ final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
+ else:
+ final_loc = pose_bone.tail
+ target.location = final_loc
+ target.keyframe_insert("location")
+ ik_constraint.mute = False
+ scene.frame_set(s_frame)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+
+def turnOffIK(enduser_obj):
+ end_bones = enduser_obj.pose.bones
+ for pose_bone in end_bones:
+ ik_constraint = hasIKConstraint(pose_bone)
+ if ik_constraint:
+ ik_constraint.mute = True
+
+
+#copy the object matrixes and clear them (to be reinserted later)
+def cleanAndStoreObjMat(performer_obj, enduser_obj):
+ perf_obj_mat = performer_obj.matrix_world.copy()
+ enduser_obj_mat = enduser_obj.matrix_world.copy()
+ zero_mat = Matrix()
+ performer_obj.matrix_world = zero_mat
+ enduser_obj.matrix_world = zero_mat
+ return perf_obj_mat, enduser_obj_mat
+
+
+#restore the object matrixes after parenting the auto generated IK empties
+def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
+ pose_bones = enduser_obj.pose.bones
+ for pose_bone in pose_bones:
+ if pose_bone.name + "Org" in bpy.data.objects:
+ empty = bpy.data.objects[pose_bone.name + "Org"]
+ empty.parent = stride_bone
+ performer_obj.matrix_world = perf_obj_mat
+ enduser_obj.parent = stride_bone
+ scene.frame_set(s_frame)
+ enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() * Matrix.Translation(stride_bone.matrix_world.to_translation())
+ enduser_obj.matrix_world = enduser_obj_mat
+
+
+#create (or return if exists) the related IK empty to the bone
+def originalLocationTarget(end_bone, enduser_obj):
+ if not end_bone.name + "IK" in enduser_obj.data.bones:
+ newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
+ newBone.head = end_bone.tail
+ newBone.tail = end_bone.tail + Vector((0, 0.1, 0))
+ else:
+ newBone = enduser_obj.pose.bones[end_bone.name + "IK"]
+ return newBone
+
+
+#create the specified NLA setup for base animation, constraints and tweak layer.
+def NLASystemInitialize(enduser_arm, context):
+ enduser_obj = context.active_object
+ NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
+ name = NLATracks.name
+ anim_data = enduser_obj.animation_data
+ s_frame = 0
+ if ("Base " + name) in bpy.data.actions:
+ mocapAction = bpy.data.actions[("Base " + name)]
+ else:
+ print("That retargeted anim has no base action")
+ anim_data.use_nla = True
+ for track in anim_data.nla_tracks:
+ anim_data.nla_tracks.remove(track)
+ mocapTrack = anim_data.nla_tracks.new()
+ mocapTrack.name = "Base " + name
+ NLATracks.base_track = mocapTrack.name
+ mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
+ constraintTrack = anim_data.nla_tracks.new()
+ constraintTrack.name = "Auto fixes " + name
+ NLATracks.auto_fix_track = constraintTrack.name
+ if ("Auto fixes " + name) in bpy.data.actions:
+ constraintAction = bpy.data.actions[("Auto fixes " + name)]
+ else:
+ constraintAction = bpy.data.actions.new("Auto fixes " + name)
+ constraintAction.use_fake_user = True
+ constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
+ constraintStrip.extrapolation = "NOTHING"
+ userTrack = anim_data.nla_tracks.new()
+ userTrack.name = "Manual fixes " + name
+ NLATracks.manual_fix_track = userTrack.name
+ if ("Manual fixes " + name) in bpy.data.actions:
+ userAction = bpy.data.actions[("Manual fixes " + name)]
+ else:
+ userAction = bpy.data.actions.new("Manual fixes " + name)
+ userAction.use_fake_user = True
+ userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
+ userStrip.extrapolation = "HOLD"
+ userStrip.blend_type = "ADD"
+ anim_data.nla_tracks.active = constraintTrack
+ anim_data.action_extrapolation = "NOTHING"
+ #set the stride_bone's action
+ if "stride_bone" in bpy.data.objects:
+ stride_bone = bpy.data.objects["stride_bone"]
+ if NLATracks.stride_action:
+ stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
+ else:
+ NLATracks.stride_action = stride_bone.animation_data.action.name
+ stride_bone.animation_data.action.use_fake_user = True
+ 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]
+ perf_root = performer_obj.pose.bones[0].name
+ for bone in map_bones:
+ perf_bone = bone.bone.reverseMap[0].name
+
+ cons = bone.constraints.new('COPY_ROTATION')
+ cons.name = "retargetTemp"
+ locks = bone.lock_rotation
+ 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
+ cons.target_space = 'WORLD'
+ cons.owner_space = 'WORLD'
+
+ if (not bone.bone.use_connect) and (perf_bone != perf_root):
+ cons = bone.constraints.new('COPY_LOCATION')
+ cons.name = "retargetTemp"
+ cons.target = performer_obj
+ cons.subtarget = perf_bone
+ cons.use_x = True
+ cons.use_y = True
+ cons.use_z = True
+ cons.target_space = 'LOCAL'
+ cons.owner_space = 'LOCAL'
+
+
+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
+ step = end_arm.frameStep
+
+ try:
+ enduser_obj.animation_data.action = bpy.data.actions.new("temp")
+ enduser_obj.animation_data.action.use_fake_user = True
+ 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)
+ 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, step)
+ print("First pass: retargeting from intermediate to end user")
+ retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
+ 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, step=step)
+ 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)
+ if not advanced:
+ IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step)
+ bpy.ops.object.select_name(name=stride_bone.name, extend=False)
+ restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ 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]:
+ NLATracks = end_arm.mocapNLATracks.add()
+ NLATracks.name = name
+ else:
+ NLATracks = end_arm.mocapNLATracks[name]
+ end_arm.active_mocap = name
+ print("retargeting done!")
+
+
+def isRigAdvanced(enduser_obj):
+ bones = enduser_obj.pose.bones
+ for bone in bones:
+ for constraint in bone.constraints:
+ if constraint.type != "IK":
+ return True
+ if enduser_obj.data.animation_data:
+ if enduser_obj.data.animation_data.drivers:
+ return True