Fix Print3D Toolbox: fix button alignment
[blender-addons.git] / mocap / mocap_constraints.py
blobd9f41ea1ab8edf7c682788d2b2f2013b46bc40ef
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
23 from bpy_extras import anim_utils
24 from . import retarget
27 ### Utility Functions
30 def getConsObj(bone):
31 #utility function - returns related IK target if bone has IK
32 ik = [constraint for constraint in bone.constraints if constraint.type == "IK"]
33 if ik:
34 ik = ik[0]
35 cons_obj = ik.target
36 if ik.subtarget:
37 cons_obj = ik.target.pose.bones[ik.subtarget]
38 else:
39 cons_obj = bone
40 return cons_obj
43 def consObjToBone(cons_obj):
44 #Utility function - returns related bone from ik object
45 if cons_obj.name[-3:] == "Org":
46 return cons_obj.name[:-3]
47 else:
48 return cons_obj.name
50 ### And and Remove Constraints (called from operators)
53 def addNewConstraint(m_constraint, cons_obj):
54 #Decide the correct Blender constraint according to the Mocap constraint type
55 if m_constraint.type == "point" or m_constraint.type == "freeze":
56 c_type = "LIMIT_LOCATION"
57 if m_constraint.type == "distance":
58 c_type = "LIMIT_DISTANCE"
59 if m_constraint.type == "floor":
60 c_type = "LIMIT_LOCATION"
61 #create and store the new constraint within m_constraint
62 real_constraint = cons_obj.constraints.new(c_type)
63 real_constraint.name = "Auto fixes " + str(len(cons_obj.constraints))
64 m_constraint.real_constraint_bone = consObjToBone(cons_obj)
65 m_constraint.real_constraint = real_constraint.name
66 #set the rest of the constraint properties
67 setConstraint(m_constraint, bpy.context)
70 def removeConstraint(m_constraint, cons_obj):
71 #remove the influence fcurve and Blender constraint
72 oldConstraint = cons_obj.constraints[m_constraint.real_constraint]
73 removeFcurves(cons_obj, bpy.context.active_object, oldConstraint, m_constraint)
74 cons_obj.constraints.remove(oldConstraint)
76 ### Update functions. There are 3: UpdateType/Bone
77 ### update framing (deals with changes in the desired frame range)
78 ### And setConstraint which deals with the rest
81 def updateConstraintBoneType(m_constraint, context):
82 #If the constraint exists, we need to remove it
83 #from the old bone
84 obj = context.active_object
85 bones = obj.pose.bones
86 if m_constraint.real_constraint:
87 bone = bones[m_constraint.real_constraint_bone]
88 cons_obj = getConsObj(bone)
89 removeConstraint(m_constraint, cons_obj)
90 #Regardless, after that we create a new constraint
91 if m_constraint.constrained_bone:
92 bone = bones[m_constraint.constrained_bone]
93 cons_obj = getConsObj(bone)
94 addNewConstraint(m_constraint, cons_obj)
97 def setConstraintFraming(m_constraint, context):
98 obj = context.active_object
99 bones = obj.pose.bones
100 bone = bones[m_constraint.constrained_bone]
101 cons_obj = getConsObj(bone)
102 real_constraint = cons_obj.constraints[m_constraint.real_constraint]
103 #remove the old keyframes
104 removeFcurves(cons_obj, obj, real_constraint, m_constraint)
105 #set the new ones according to the m_constraint properties
106 s, e = m_constraint.s_frame, m_constraint.e_frame
107 s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
108 real_constraint.influence = 1
109 real_constraint.keyframe_insert(data_path="influence", frame=s)
110 real_constraint.keyframe_insert(data_path="influence", frame=e)
111 real_constraint.influence = 0
112 real_constraint.keyframe_insert(data_path="influence", frame=s - s_in)
113 real_constraint.keyframe_insert(data_path="influence", frame=e + s_out)
116 def removeFcurves(cons_obj, obj, real_constraint, m_constraint):
117 #Determine if the constrained object is a bone or an empty
118 if isinstance(cons_obj, bpy.types.PoseBone):
119 fcurves = obj.animation_data.action.fcurves
120 else:
121 fcurves = cons_obj.animation_data.action.fcurves
122 #Find the RNA data path of the constraint's influence
123 RNA_paths = []
124 RNA_paths.append(real_constraint.path_from_id("influence"))
125 if m_constraint.type == "floor" or m_constraint.type == "point":
126 RNA_paths += [real_constraint.path_from_id("max_x"), real_constraint.path_from_id("min_x")]
127 RNA_paths += [real_constraint.path_from_id("max_y"), real_constraint.path_from_id("min_y")]
128 RNA_paths += [real_constraint.path_from_id("max_z"), real_constraint.path_from_id("min_z")]
129 #Retrieve the correct fcurve via the RNA data path and remove it
130 fcurves_del = [fcurve for fcurve in fcurves if fcurve.data_path in RNA_paths]
131 #clear the fcurve and set the frames.
132 if fcurves_del:
133 for fcurve in fcurves_del:
134 fcurves.remove(fcurve)
135 #remove armature fcurves (if user keyframed m_constraint properties)
136 if obj.data.animation_data and m_constraint.type == "point":
137 if obj.data.animation_data.action:
138 path = m_constraint.path_from_id("targetPoint")
139 m_fcurves = [fcurve for fcurve in obj.data.animation_data.action.fcurves if fcurve.data_path == path]
140 for curve in m_fcurves:
141 obj.data.animation_data.action.fcurves.remove(curve)
143 #Utility function for copying property fcurves over
146 def copyFCurve(newCurve, oldCurve):
147 for point in oldCurve.keyframe_points:
148 newCurve.keyframe_points.insert(frame=point.co.x, value=point.co.y)
150 #Creates new fcurves for the constraint properties (for floor and point)
153 def createConstraintFCurves(cons_obj, obj, real_constraint):
154 if isinstance(cons_obj, bpy.types.PoseBone):
155 c_fcurves = obj.animation_data.action.fcurves
156 else:
157 c_fcurves = cons_obj.animation_data.action.fcurves
158 c_x_path = [real_constraint.path_from_id("max_x"), real_constraint.path_from_id("min_x")]
159 c_y_path = [real_constraint.path_from_id("max_y"), real_constraint.path_from_id("min_y")]
160 c_z_path = [real_constraint.path_from_id("max_z"), real_constraint.path_from_id("min_z")]
161 c_constraints_path = c_x_path + c_y_path + c_z_path
162 existing_curves = [fcurve for fcurve in c_fcurves if fcurve.data_path in c_constraints_path]
163 if existing_curves:
164 for curve in existing_curves:
165 c_fcurves.remove(curve)
166 xCurves, yCurves, zCurves = [], [], []
167 for path in c_constraints_path:
168 newCurve = c_fcurves.new(path)
169 if path in c_x_path:
170 xCurves.append(newCurve)
171 elif path in c_y_path:
172 yCurves.append(newCurve)
173 else:
174 zCurves.append(newCurve)
175 return xCurves, yCurves, zCurves
178 # Function that copies all settings from m_constraint to the real Blender constraints
179 # Is only called when blender constraint already exists
182 def setConstraint(m_constraint, context):
183 if not m_constraint.constrained_bone:
184 return
185 obj = context.active_object
186 bones = obj.pose.bones
187 bone = bones[m_constraint.constrained_bone]
188 cons_obj = getConsObj(bone)
189 real_constraint = cons_obj.constraints[m_constraint.real_constraint]
190 NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
191 obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
193 #frame changing section
194 setConstraintFraming(m_constraint, context)
195 s, e = m_constraint.s_frame, m_constraint.e_frame
196 s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
197 s -= s_in
198 e += s_out
199 #Set the blender constraint parameters
200 if m_constraint.type == "point":
201 constraint_settings = False # are fix settings keyframed?
202 if not m_constraint.targetSpace == "constrained_boneB":
203 real_constraint.owner_space = m_constraint.targetSpace
204 else:
205 real_constraint.owner_space = "LOCAL"
206 if obj.data.animation_data:
207 if obj.data.animation_data.action:
208 path = m_constraint.path_from_id("targetPoint")
209 m_fcurves = [fcurve for fcurve in obj.data.animation_data.action.fcurves if fcurve.data_path == path]
210 if m_fcurves:
211 constraint_settings = True
212 xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
213 for curve in xCurves:
214 copyFCurve(curve, m_fcurves[0])
215 for curve in yCurves:
216 copyFCurve(curve, m_fcurves[1])
217 for curve in zCurves:
218 copyFCurve(curve, m_fcurves[2])
219 if m_constraint.targetSpace == "constrained_boneB" and m_constraint.constrained_boneB:
220 c_frame = context.scene.frame_current
221 bakedPos = {}
222 src_bone = bones[m_constraint.constrained_boneB]
223 if not constraint_settings:
224 xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
225 print("please wait a moment, calculating fix")
226 for t in range(s, e):
227 context.scene.frame_set(t)
228 src_bone_pos = src_bone.matrix.to_translation()
229 bakedPos[t] = src_bone_pos + m_constraint.targetPoint # final position for constrained bone in object space
230 context.scene.frame_set(c_frame)
231 for frame in bakedPos.keys():
232 pos = bakedPos[frame]
233 for xCurve in xCurves:
234 xCurve.keyframe_points.insert(frame=frame, value=pos.x)
235 for yCurve in yCurves:
236 yCurve.keyframe_points.insert(frame=frame, value=pos.y)
237 for zCurve in zCurves:
238 zCurve.keyframe_points.insert(frame=frame, value=pos.z)
240 if not constraint_settings:
241 x, y, z = m_constraint.targetPoint
242 real_constraint.max_x = x
243 real_constraint.max_y = y
244 real_constraint.max_z = z
245 real_constraint.min_x = x
246 real_constraint.min_y = y
247 real_constraint.min_z = z
248 real_constraint.use_max_x = True
249 real_constraint.use_max_y = True
250 real_constraint.use_max_z = True
251 real_constraint.use_min_x = True
252 real_constraint.use_min_y = True
253 real_constraint.use_min_z = True
255 if m_constraint.type == "freeze":
256 context.scene.frame_set(s)
257 real_constraint.owner_space = m_constraint.targetSpace
258 bpy.context.scene.frame_set(m_constraint.s_frame)
259 if isinstance(cons_obj, bpy.types.PoseBone):
260 vec = obj.matrix_world * (cons_obj.matrix.to_translation())
261 #~ if obj.parent:
262 #~ vec = obj.parent.matrix_world * vec
263 x, y, z = vec
264 else:
265 x, y, z = cons_obj.matrix_world.to_translation()
267 real_constraint.max_x = x
268 real_constraint.max_y = y
269 real_constraint.max_z = z
270 real_constraint.min_x = x
271 real_constraint.min_y = y
272 real_constraint.min_z = z
273 real_constraint.use_max_x = True
274 real_constraint.use_max_y = True
275 real_constraint.use_max_z = True
276 real_constraint.use_min_x = True
277 real_constraint.use_min_y = True
278 real_constraint.use_min_z = True
280 if m_constraint.type == "distance" and m_constraint.constrained_boneB:
281 real_constraint.owner_space = "WORLD"
282 real_constraint.target = obj
283 real_constraint.subtarget = getConsObj(bones[m_constraint.constrained_boneB]).name
284 real_constraint.limit_mode = "LIMITDIST_ONSURFACE"
285 if m_constraint.targetDist < 0.01:
286 m_constraint.targetDist = 0.01
287 real_constraint.distance = m_constraint.targetDist
289 if m_constraint.type == "floor" and m_constraint.targetMesh:
290 real_constraint.mute = True
291 real_constraint.owner_space = "WORLD"
292 #calculate the positions thoughout the range
293 s, e = m_constraint.s_frame, m_constraint.e_frame
294 s_in, s_out = m_constraint.smooth_in, m_constraint.smooth_out
295 s -= s_in
296 e += s_out
297 bakedPos = {}
298 floor = bpy.data.objects[m_constraint.targetMesh]
299 c_frame = context.scene.frame_current
300 print("please wait a moment, calculating fix")
301 for t in range(s, e):
302 context.scene.frame_set(t)
303 axis = obj.matrix_world.to_3x3() * Vector((0, 0, 100))
304 offset = obj.matrix_world.to_3x3() * Vector((0, 0, m_constraint.targetDist))
305 ray_origin = (cons_obj.matrix * obj.matrix_world).to_translation() - offset # world position of constrained bone
306 ray_target = ray_origin + axis
307 #convert ray points to floor's object space
308 ray_origin = floor.matrix_world.inverted() * ray_origin
309 ray_target = floor.matrix_world.inverted() * ray_target
310 hit, nor, ind = floor.ray_cast(ray_origin, ray_target)
311 if hit != Vector((0, 0, 0)):
312 bakedPos[t] = (floor.matrix_world * hit)
313 bakedPos[t] += Vector((0, 0, m_constraint.targetDist))
314 else:
315 bakedPos[t] = (cons_obj.matrix * obj.matrix_world).to_translation()
316 context.scene.frame_set(c_frame)
317 #create keyframes for real constraint
318 xCurves, yCurves, zCurves = createConstraintFCurves(cons_obj, obj, real_constraint)
319 for frame in bakedPos.keys():
320 pos = bakedPos[frame]
321 for xCurve in xCurves:
322 xCurve.keyframe_points.insert(frame=frame, value=pos.x)
323 for yCurve in yCurves:
324 yCurve.keyframe_points.insert(frame=frame, value=pos.y)
325 for zCurve in zCurves:
326 zCurve.keyframe_points.insert(frame=frame, value=pos.z)
327 real_constraint.use_max_x = True
328 real_constraint.use_max_y = True
329 real_constraint.use_max_z = True
330 real_constraint.use_min_x = True
331 real_constraint.use_min_y = True
332 real_constraint.use_min_z = True
334 # active/baked check
335 real_constraint.mute = (not m_constraint.active)
338 def locBake(s_frame, e_frame, bones):
339 scene = bpy.context.scene
340 bakeDict = {}
341 for bone in bones:
342 bakeDict[bone.name] = {}
343 for t in range(s_frame, e_frame):
344 scene.frame_set(t)
345 for bone in bones:
346 bakeDict[bone.name][t] = bone.matrix.copy()
347 for t in range(s_frame, e_frame):
348 for bone in bones:
349 print(bone.bone.matrix_local.to_translation())
350 bone.matrix = bakeDict[bone.name][t]
351 bone.keyframe_insert("location", frame=t)
354 # Baking function which bakes all bones effected by the constraint
355 def bakeAllConstraints(obj, s_frame, e_frame, bones):
356 for bone in bones:
357 bone.bone.select = False
358 selectedBones = [] # Marks bones that need a full bake
359 simpleBake = [] # Marks bones that need only a location bake
360 for end_bone in bones:
361 if end_bone.name in [m_constraint.real_constraint_bone for m_constraint in obj.data.mocap_constraints]:
362 #For all bones that have a constraint:
363 ik = retarget.hasIKConstraint(end_bone)
364 cons_obj = getConsObj(end_bone)
365 if ik:
366 #If it's an auto generated IK:
367 if ik.chain_count == 0:
368 selectedBones += bones # Chain len 0, bake everything
369 else:
370 selectedBones += [end_bone] + end_bone.parent_recursive[:ik.chain_count - 1] # Bake the chain
371 else:
372 #It's either an FK bone which we should just bake
373 #OR a user created IK target bone
374 simpleBake += [end_bone]
375 for bone in selectedBones:
376 bone.bone.select = True
377 NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
378 obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
379 constraintTrack = obj.animation_data.nla_tracks[NLATracks.auto_fix_track]
380 constraintStrip = constraintTrack.strips[0]
381 constraintStrip.action_frame_start = s_frame
382 constraintStrip.action_frame_end = e_frame
383 constraintStrip.frame_start = s_frame
384 constraintStrip.frame_end = e_frame
385 if selectedBones:
386 # Use bake function from NLA Bake Action operator
387 anim_utils.bake_action(s_frame,
388 e_frame,
389 action=constraintStrip.action,
390 only_selected=True,
391 do_pose=True,
392 do_object=False,
394 if simpleBake:
395 #Do a "simple" bake, location only, world space only.
396 locBake(s_frame, e_frame, simpleBake)
399 #Calls the baking function and decativates releveant constraints
400 def bakeConstraints(context):
401 obj = context.active_object
402 bones = obj.pose.bones
403 s_frame, e_frame = context.scene.frame_start, context.scene.frame_end
404 #Bake relevant bones
405 bakeAllConstraints(obj, s_frame, e_frame, bones)
406 for m_constraint in obj.data.mocap_constraints:
407 end_bone = bones[m_constraint.real_constraint_bone]
408 cons_obj = getConsObj(end_bone)
409 # It's a control empty: turn the ik off
410 if not isinstance(cons_obj, bpy.types.PoseBone):
411 ik_con = retarget.hasIKConstraint(end_bone)
412 if ik_con:
413 ik_con.mute = True
414 # Deactivate related Blender Constraint
415 m_constraint.active = False
418 #Deletes the baked fcurves and reactivates relevant constraints
419 def unbakeConstraints(context):
420 # to unbake constraints we delete the whole strip
421 obj = context.active_object
422 bones = obj.pose.bones
423 scene = bpy.context.scene
424 NLATracks = obj.data.mocapNLATracks[obj.data.active_mocap]
425 obj.animation_data.action = bpy.data.actions[NLATracks.auto_fix_track]
426 constraintTrack = obj.animation_data.nla_tracks[NLATracks.auto_fix_track]
427 constraintStrip = constraintTrack.strips[0]
428 action = constraintStrip.action
429 # delete the fcurves on the strip
430 for fcurve in action.fcurves:
431 action.fcurves.remove(fcurve)
432 # reactivate relevant constraints
433 for m_constraint in obj.data.mocap_constraints:
434 end_bone = bones[m_constraint.real_constraint_bone]
435 cons_obj = getConsObj(end_bone)
436 # It's a control empty: turn the ik back on
437 if not isinstance(cons_obj, bpy.types.PoseBone):
438 ik_con = retarget.hasIKConstraint(end_bone)
439 if ik_con:
440 ik_con.mute = False
441 m_constraint.active = True
444 def updateConstraints(obj, context):
445 fixes = obj.data.mocap_constraints
446 for fix in fixes:
447 fix.active = False
448 fix.active = True