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