Extensions: lock the repositories before overwriting their manifests
[blender-addons-contrib.git] / mocap / retarget.py
blob5c963c056d01d267acaf77551977f84ebe9a665b
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 import bpy
20 from mathutils import Vector, Matrix
21 from math import radians
22 from bpy_extras.anim_utils import bake_action
25 def hasIKConstraint(pose_bone):
26 #utility function / predicate, returns True if given bone has IK constraint
27 ik = [constraint for constraint in pose_bone.constraints if constraint.type == "IK"]
28 if ik:
29 return ik[0]
30 else:
31 return False
34 def createDictionary(perf_arm, end_arm):
35 # clear any old data
36 for end_bone in end_arm.bones:
37 for mapping in end_bone.reverseMap:
38 end_bone.reverseMap.remove(0)
40 for perf_bone in perf_arm.bones:
41 #find its match and add perf_bone to the match's mapping
42 if perf_bone.map:
43 end_bone = end_arm.bones[perf_bone.map]
44 newMap = end_bone.reverseMap.add()
45 newMap.name = perf_bone.name
46 end_bone.foot = perf_bone.foot
48 #root is the root of the enduser
49 root = end_arm.bones[0].name
50 feetBones = [bone.name for bone in perf_arm.bones if bone.foot]
51 return feetBones, root
54 def loadMapping(perf_arm, end_arm):
55 for end_bone in end_arm.bones:
56 #find its match and add perf_bone to the match's mapping
57 if end_bone.reverseMap:
58 for perf_bone in end_bone.reverseMap:
59 perf_arm.bones[perf_bone.name].map = end_bone.name
61 #creation of intermediate armature
62 # the intermediate armature has the hierarchy of the end user,
63 # does not have rotation inheritance
64 # and bone roll is identical to the performer
65 # its purpose is to copy over the rotations
66 # easily while concentrating on the hierarchy changes
69 def createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step):
70 #creates and keyframes an empty with its location
71 #the original position of the tail bone
72 #useful for storing the important data in the original motion
73 #i.e. using this empty to IK the chain to that pos / DEBUG
75 #Simple 1to1 retarget of a bone
76 def singleBoneRetarget(inter_bone, perf_bone):
77 perf_world_rotation = perf_bone.matrix
78 inter_world_base_rotation = inter_bone.bone.matrix_local
79 inter_world_base_inv = inter_world_base_rotation.inverted()
80 bake_matrix = inter_world_base_inv.to_3x3() @ perf_world_rotation.to_3x3()
81 return bake_matrix.to_4x4()
83 #uses 1to1 and interpolation/averaging to match many to 1 retarget
84 def manyPerfToSingleInterRetarget(inter_bone, performer_bones_s):
85 retarget_matrices = [singleBoneRetarget(inter_bone, perf_bone) for perf_bone in performer_bones_s]
86 lerp_matrix = Matrix()
87 for i in range(len(retarget_matrices) - 1):
88 first_mat = retarget_matrices[i]
89 next_mat = retarget_matrices[i + 1]
90 lerp_matrix = first_mat.lerp(next_mat, 0.5)
91 return lerp_matrix
93 #determines the type of hierarchy change needed and calls the
94 #right function
95 def retargetPerfToInter(inter_bone):
96 if inter_bone.bone.reverseMap:
97 perf_bone_name = inter_bone.bone.reverseMap
98 # 1 to many not supported yet
99 # then its either a many to 1 or 1 to 1
100 if len(perf_bone_name) > 1:
101 performer_bones_s = [performer_bones[map.name] for map in perf_bone_name]
102 #we need to map several performance bone to a single
103 inter_bone.matrix_basis = manyPerfToSingleInterRetarget(inter_bone, performer_bones_s)
104 else:
105 perf_bone = performer_bones[perf_bone_name[0].name]
106 inter_bone.matrix_basis = singleBoneRetarget(inter_bone, perf_bone)
107 #Some bones have incorrect roll on the source armature, and need to be marked for fixing
108 if inter_bone.bone.twistFix:
109 inter_bone.matrix_basis @= Matrix.Rotation(radians(180), 4, "Y")
110 rot_mode = inter_bone.rotation_mode
111 if rot_mode == "QUATERNION":
112 inter_bone.keyframe_insert("rotation_quaternion")
113 elif rot_mode == "AXIS_ANGLE":
114 inter_bone.keyframe_insert("rotation_axis_angle")
115 else:
116 inter_bone.keyframe_insert("rotation_euler")
118 #creates the intermediate armature object
119 inter_obj = enduser_obj.copy()
120 inter_obj.data = inter_obj.data.copy() # duplicate data
121 bpy.context.collection.objects.link(inter_obj)
122 inter_obj.name = "intermediate"
123 bpy.context.view_layer.objects.active = inter_obj
124 bpy.ops.object.mode_set(mode='EDIT')
125 #add some temporary connecting bones in case end user bones are not connected to their parents
126 rollDict = {}
127 print("creating temp bones")
128 for bone in inter_obj.data.edit_bones:
129 if not bone.use_connect and bone.parent:
130 if inter_obj.data.bones[bone.parent.name].reverseMap or inter_obj.data.bones[bone.name].reverseMap:
131 newBone = inter_obj.data.edit_bones.new("Temp")
132 newBone.head = bone.parent.tail
133 newBone.tail = bone.head
134 newBone.parent = bone.parent
135 bone.parent = newBone
136 bone.use_connect = True
137 newBone.use_connect = True
138 rollDict[bone.name] = bone.roll
139 bone.roll = 0
140 #resets roll
141 print("retargeting to intermediate")
142 bpy.ops.object.mode_set(mode="OBJECT")
143 inter_obj.data.name = "inter_arm"
144 inter_arm = inter_obj.data
145 performer_bones = performer_obj.pose.bones
146 inter_bones = inter_obj.pose.bones
147 #clears inheritance
148 for inter_bone in inter_bones:
149 if inter_bone.bone.reverseMap:
150 inter_bone.bone.use_inherit_rotation = False
151 else:
152 inter_bone.bone.use_inherit_rotation = True
154 for t in range(s_frame, e_frame, step):
155 if (t - s_frame) % 10 == 0:
156 print("First pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
157 scene.frame_set(t)
158 for bone in inter_bones:
159 retargetPerfToInter(bone)
161 return inter_obj
163 # this procedure copies the rotations over from the intermediate
164 # armature to the end user one.
165 # As the hierarchies are 1 to 1, this is a simple matter of
166 # copying the rotation, while keeping in mind bone roll, parenting, etc.
167 # TODO: Control Bones: If a certain bone is constrained in a way
168 # that its rotation is determined by another (a control bone)
169 # We should determine the right pos of the control bone.
170 # Scale: ? Should work but needs testing.
173 def retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step):
174 inter_bones = inter_obj.pose.bones
175 end_bones = enduser_obj.pose.bones
177 #Basic "visual baking" function, for transferring rotations from intermediate to end user
178 def bakeTransform(end_bone):
179 src_bone = inter_bones[end_bone.name]
180 trg_bone = end_bone
181 bake_matrix = src_bone.matrix
182 rest_matrix = trg_bone.bone.matrix_local
184 if trg_bone.parent and trg_bone.bone.use_inherit_rotation:
185 srcParent = src_bone.parent
186 if "Temp" in srcParent.name:
187 srcParent = srcParent.parent
188 parent_mat = srcParent.matrix
189 parent_rest = trg_bone.parent.bone.matrix_local
190 parent_rest_inv = parent_rest.inverted()
191 parent_mat_inv = parent_mat.inverted()
192 bake_matrix = parent_mat_inv @ bake_matrix
193 rest_matrix = parent_rest_inv @ rest_matrix
195 rest_matrix_inv = rest_matrix.inverted()
196 bake_matrix = rest_matrix_inv @ bake_matrix
197 end_bone.matrix_basis = bake_matrix
198 rot_mode = end_bone.rotation_mode
199 if rot_mode == "QUATERNION":
200 end_bone.keyframe_insert("rotation_quaternion")
201 elif rot_mode == "AXIS_ANGLE":
202 end_bone.keyframe_insert("rotation_axis_angle")
203 else:
204 end_bone.keyframe_insert("rotation_euler")
205 if not end_bone.bone.use_connect:
206 end_bone.keyframe_insert("location")
208 for bone in end_bone.children:
209 bakeTransform(bone)
211 for t in range(s_frame, e_frame, step):
212 if (t - s_frame) % 10 == 0:
213 print("Second pass: retargeting frame {0}/{1}".format(t, e_frame - s_frame))
214 scene.frame_set(t)
215 end_bone = end_bones[root]
216 end_bone.location = Vector((0, 0, 0))
217 end_bone.keyframe_insert("location")
218 bakeTransform(end_bone)
220 #receives the performer feet bones as a variable
221 # by "feet" I mean those bones that have plants
222 # (they don't move, despite root moving) somewhere in the animation.
225 def copyTranslation(performer_obj, enduser_obj, perfFeet, root, s_frame, e_frame, scene, enduser_obj_mat):
227 perf_bones = performer_obj.pose.bones
228 end_bones = enduser_obj.pose.bones
230 perfRoot = perf_bones[0].name
231 endFeet = [perf_bones[perfBone].bone.map for perfBone in perfFeet]
232 locDictKeys = perfFeet + endFeet + [perfRoot]
234 def tailLoc(bone):
235 return bone.center + (bone.vector / 2)
237 #Step 1 - we create a dict that contains these keys:
238 #(Performer) Hips, Feet
239 #(End user) Feet
240 # where the values are their world position on each frame in range (s,e)
242 locDict = {}
243 for key in locDictKeys:
244 locDict[key] = []
246 for t in range(scene.frame_start, scene.frame_end):
247 scene.frame_set(t)
248 for bone in perfFeet:
249 locDict[bone].append(tailLoc(perf_bones[bone]))
250 locDict[perfRoot].append(tailLoc(perf_bones[perfRoot]))
251 for bone in endFeet:
252 locDict[bone].append(tailLoc(end_bones[bone]))
254 # now we take our locDict and analyze it.
255 # we need to derive all chains
257 def locDeriv(key, t):
258 graph = locDict[key]
259 return graph[t + 1] - graph[t]
261 # now find the plant frames, where perfFeet don't move much
263 linearAvg = []
264 for key in perfFeet:
265 for i in range(len(locDict[key]) - 1):
266 v = locDeriv(key, i)
267 if (v.length < 0.1):
268 hipV = locDeriv(perfRoot, i)
269 endV = locDeriv(perf_bones[key].bone.map, i)
270 #this is a plant frame.
271 #lets see what the original hip delta is, and the corresponding
272 #end bone's delta
273 if endV.length != 0:
274 linearAvg.append(hipV.length / endV.length)
276 action_name = performer_obj.animation_data.action.name
277 #is there a stride_bone?
278 if "stride_bone" in bpy.data.objects:
279 stride_action = bpy.data.actions.new("Stride Bone " + action_name)
280 stride_action.use_fake_user = True
281 stride_bone = enduser_obj.parent
282 stride_bone.animation_data.action = stride_action
283 else:
284 bpy.ops.object.mode_set(mode='OBJECT')
285 bpy.ops.object.add()
286 stride_bone = bpy.context.active_object
287 stride_bone.name = "stride_bone"
288 stride_bone.location = enduser_obj_mat.to_translation()
289 if linearAvg:
290 #determine the average change in scale needed
291 avg = sum(linearAvg) / len(linearAvg)
292 else:
293 avg = 1
294 scene.frame_set(s_frame)
295 initialPos = (tailLoc(perf_bones[perfRoot]) / avg)
296 for t in range(s_frame, e_frame):
297 scene.frame_set(t)
298 #calculate the new position, by dividing by the found ratio between performer and enduser
299 newTranslation = (tailLoc(perf_bones[perfRoot]) / avg)
300 stride_bone.location = enduser_obj_mat @ (newTranslation - initialPos)
301 stride_bone.keyframe_insert("location")
302 stride_bone.animation_data.action.name = ("Stride Bone " + action_name)
304 return stride_bone
307 def IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step):
308 end_bones = enduser_obj.pose.bones
309 for pose_bone in end_bones:
310 ik_constraint = hasIKConstraint(pose_bone)
311 if ik_constraint:
312 target_is_bone = False
313 # set constraint target to corresponding empty if targetless,
314 # if not, keyframe current target to corresponding empty
315 perf_bone = pose_bone.bone.reverseMap[-1].name
316 bpy.ops.object.mode_set(mode='EDIT')
317 orgLocTrg = originalLocationTarget(pose_bone, enduser_obj)
318 bpy.ops.object.mode_set(mode='OBJECT')
319 if not ik_constraint.target:
320 ik_constraint.target = enduser_obj
321 ik_constraint.subtarget = pose_bone.name + "IK"
322 target = orgLocTrg
324 # There is a target now
325 if ik_constraint.subtarget:
326 target = ik_constraint.target.pose.bones[ik_constraint.subtarget]
327 target.bone.use_local_location = False
328 target_is_bone = True
329 else:
330 target = ik_constraint.target
332 # bake the correct locations for the ik target bones
333 for t in range(s_frame, e_frame, step):
334 scene.frame_set(t)
335 if target_is_bone:
336 final_loc = pose_bone.tail - target.bone.matrix_local.to_translation()
337 else:
338 final_loc = pose_bone.tail
339 target.location = final_loc
340 target.keyframe_insert("location")
341 ik_constraint.mute = False
342 scene.frame_set(s_frame)
343 bpy.ops.object.mode_set(mode='OBJECT')
346 def turnOffIK(enduser_obj):
347 end_bones = enduser_obj.pose.bones
348 for pose_bone in end_bones:
349 ik_constraint = hasIKConstraint(pose_bone)
350 if ik_constraint:
351 ik_constraint.mute = True
354 #copy the object matrixes and clear them (to be reinserted later)
355 def cleanAndStoreObjMat(performer_obj, enduser_obj):
356 perf_obj_mat = performer_obj.matrix_world.copy()
357 enduser_obj_mat = enduser_obj.matrix_world.copy()
358 zero_mat = Matrix()
359 performer_obj.matrix_world = zero_mat
360 enduser_obj.matrix_world = zero_mat
361 return perf_obj_mat, enduser_obj_mat
364 #restore the object matrixes after parenting the auto generated IK empties
365 def restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame):
366 pose_bones = enduser_obj.pose.bones
367 for pose_bone in pose_bones:
368 if pose_bone.name + "Org" in bpy.data.objects:
369 empty = bpy.data.objects[pose_bone.name + "Org"]
370 empty.parent = stride_bone
371 performer_obj.matrix_world = perf_obj_mat
372 enduser_obj.parent = stride_bone
373 scene.frame_set(s_frame)
374 enduser_obj_mat = enduser_obj_mat.to_3x3().to_4x4() @ Matrix.Translation(stride_bone.matrix_world.to_translation())
375 enduser_obj.matrix_world = enduser_obj_mat
378 #create (or return if exists) the related IK empty to the bone
379 def originalLocationTarget(end_bone, enduser_obj):
380 ik_bone = hasIKConstraint(end_bone).subtarget
381 if not ik_bone:
382 print("Adding IK bones for: " + end_bone.name)
383 newBone = enduser_obj.data.edit_bones.new(end_bone.name + "IK")
384 newBone.head = end_bone.tail
385 newBone.tail = end_bone.tail + Vector((0, 0.1, 0))
386 else:
387 newBone = enduser_obj.pose.bones[ik_bone]
388 return newBone
391 #create the specified NLA setup for base animation, constraints and tweak layer.
392 def NLASystemInitialize(enduser_arm, context):
393 enduser_obj = context.active_object
394 NLATracks = enduser_arm.mocapNLATracks[enduser_obj.data.active_mocap]
395 name = NLATracks.name
396 anim_data = enduser_obj.animation_data
397 s_frame = 0
398 if ("Base " + name) in bpy.data.actions:
399 mocapAction = bpy.data.actions[("Base " + name)]
400 else:
401 print("That retargeted anim has no base action")
402 anim_data.use_nla = True
403 for track in anim_data.nla_tracks:
404 anim_data.nla_tracks.remove(track)
405 mocapTrack = anim_data.nla_tracks.new()
406 mocapTrack.name = "Base " + name
407 NLATracks.base_track = mocapTrack.name
408 mocapStrip = mocapTrack.strips.new("Base " + name, s_frame, mocapAction)
409 constraintTrack = anim_data.nla_tracks.new()
410 constraintTrack.name = "Auto fixes " + name
411 NLATracks.auto_fix_track = constraintTrack.name
412 if ("Auto fixes " + name) in bpy.data.actions:
413 constraintAction = bpy.data.actions[("Auto fixes " + name)]
414 else:
415 constraintAction = bpy.data.actions.new("Auto fixes " + name)
416 constraintAction.use_fake_user = True
417 constraintStrip = constraintTrack.strips.new("Auto fixes " + name, s_frame, constraintAction)
418 constraintStrip.extrapolation = "NOTHING"
419 userTrack = anim_data.nla_tracks.new()
420 userTrack.name = "Manual fixes " + name
421 NLATracks.manual_fix_track = userTrack.name
422 if ("Manual fixes " + name) in bpy.data.actions:
423 userAction = bpy.data.actions[("Manual fixes " + name)]
424 else:
425 userAction = bpy.data.actions.new("Manual fixes " + name)
426 userAction.use_fake_user = True
427 userStrip = userTrack.strips.new("Manual fixes " + name, s_frame, userAction)
428 userStrip.extrapolation = "HOLD"
429 userStrip.blend_type = "ADD"
430 anim_data.nla_tracks.active = constraintTrack
431 anim_data.action_extrapolation = "NOTHING"
432 #set the stride_bone's action
433 if "stride_bone" in bpy.data.objects:
434 stride_bone = bpy.data.objects["stride_bone"]
435 if NLATracks.stride_action:
436 stride_bone.animation_data.action = bpy.data.actions[NLATracks.stride_action]
437 else:
438 NLATracks.stride_action = stride_bone.animation_data.action.name
439 stride_bone.animation_data.action.use_fake_user = True
440 anim_data.action = None
443 def preAdvancedRetargeting(performer_obj, enduser_obj):
444 createDictionary(performer_obj.data, enduser_obj.data)
445 bones = enduser_obj.pose.bones
446 map_bones = [bone for bone in bones if bone.bone.reverseMap]
447 perf_root = performer_obj.pose.bones[0].name
448 for bone in map_bones:
449 perf_bone = bone.bone.reverseMap[0].name
451 cons = bone.constraints.new('COPY_ROTATION')
452 cons.name = "retargetTemp"
453 locks = bone.lock_rotation
454 cons.use_x = not locks[0]
455 cons.use_y = not locks[1]
456 cons.use_z = not locks[2]
457 cons.target = performer_obj
458 cons.subtarget = perf_bone
459 cons.target_space = 'WORLD'
460 cons.owner_space = 'WORLD'
462 if (not bone.bone.use_connect) and (perf_bone != perf_root):
463 cons = bone.constraints.new('COPY_LOCATION')
464 cons.name = "retargetTemp"
465 cons.target = performer_obj
466 cons.subtarget = perf_bone
467 cons.use_x = True
468 cons.use_y = True
469 cons.use_z = True
470 if bone.bone.poseSpace:
471 cons.target_space = 'POSE'
472 cons.owner_space = 'POSE'
473 else:
474 cons.target_space = 'LOCAL'
475 cons.owner_space = 'LOCAL'
477 def prepareForBake(enduser_obj):
478 bones = enduser_obj.pose.bones
479 for bone in bones:
480 bone.bone.select = False
481 map_bones = [bone for bone in bones if bone.bone.reverseMap]
482 for bone in map_bones:
483 for cons in bone.constraints:
484 if "retargetTemp" in cons.name:
485 bone.bone.select = True
488 def cleanTempConstraints(enduser_obj):
489 bones = enduser_obj.pose.bones
490 map_bones = [bone for bone in bones if bone.bone.reverseMap]
491 for bone in map_bones:
492 for cons in bone.constraints:
493 if "retargetTemp" in cons.name:
494 bone.constraints.remove(cons)
497 #Main function that runs the retargeting sequence.
498 #If advanced == True, we assume constraint's were already created
499 def totalRetarget(performer_obj, enduser_obj, scene, s_frame, e_frame):
500 perf_arm = performer_obj.data
501 end_arm = enduser_obj.data
502 advanced = end_arm.advancedRetarget
503 step = end_arm.frameStep
504 enduser_obj.animation_data_create()
506 try:
507 enduser_obj.animation_data.action = bpy.data.actions.new("temp")
508 enduser_obj.animation_data.action.use_fake_user = True
509 except:
510 print("no need to create new action")
512 print("creating Dictionary")
513 feetBones, root = createDictionary(perf_arm, end_arm)
514 print("cleaning stuff up")
515 perf_obj_mat, enduser_obj_mat = cleanAndStoreObjMat(performer_obj, enduser_obj)
516 if not advanced:
517 turnOffIK(enduser_obj)
518 print("Creating intermediate armature (for first pass)")
519 inter_obj = createIntermediate(performer_obj, enduser_obj, root, s_frame, e_frame, scene, step)
520 print("First pass: retargeting from intermediate to end user")
521 retargetEnduser(inter_obj, enduser_obj, root, s_frame, e_frame, scene, step)
522 else:
523 prepareForBake(enduser_obj)
524 print("Retargeting pose (Advanced Retarget)")
525 bake_action(
526 bpy.context.object,
527 s_frame, e_frame,
528 action=enduser_obj.animation_data.action,
529 only_selected=True,
530 do_pose=True,
531 do_object=False,
532 frame_step=step,
534 name = performer_obj.animation_data.action.name[:10]
535 #We trim the name down to 10 chars because of Action Name length maximum
536 enduser_obj.animation_data.action.name = "Base " + name
537 print("Second pass: retargeting root translation and clean up")
538 stride_bone = copyTranslation(performer_obj, enduser_obj, feetBones, root, s_frame, e_frame, scene, enduser_obj_mat)
539 if not advanced:
540 print("hry")
541 bpy.ops.object.select_all(action='DESELECT')
542 bpy.context.view_layer.objects.active = enduser_obj
543 bpy.ops.object.select_pattern(pattern=enduser_obj.name, extend=False)
544 IKRetarget(performer_obj, enduser_obj, s_frame, e_frame, scene, step)
545 bpy.ops.object.select_pattern(pattern=stride_bone.name, extend=False)
546 restoreObjMat(performer_obj, enduser_obj, perf_obj_mat, enduser_obj_mat, stride_bone, scene, s_frame)
547 bpy.ops.object.mode_set(mode='OBJECT')
548 if not advanced:
549 bpy.ops.object.select_pattern(pattern=inter_obj.name, extend=False)
550 bpy.ops.object.delete()
551 else:
552 cleanTempConstraints(enduser_obj)
553 bpy.ops.object.select_pattern(pattern=enduser_obj.name, extend=False)
555 if not name in [tracks.name for tracks in end_arm.mocapNLATracks]:
556 NLATracks = end_arm.mocapNLATracks.add()
557 NLATracks.name = name
558 else:
559 NLATracks = end_arm.mocapNLATracks[name]
560 end_arm.active_mocap = name
561 print("retargeting done!")
564 def isRigAdvanced(enduser_obj):
565 bones = enduser_obj.pose.bones
566 for bone in bones:
567 for constraint in bone.constraints:
568 if constraint.type != "IK":
569 return True
570 if enduser_obj.data.animation_data:
571 if enduser_obj.data.animation_data.drivers:
572 return True