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 #####
22
from mathutils import Vector
23
from bpy_extras import anim_utils
24
from . import retarget
31
#utility function - returns related IK target if bone has IK
32
ik = [constraint for constraint in bone.constraints if constraint.type == "IK"]
37
cons_obj = ik.target.pose.bones[ik.subtarget]
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]
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
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
121
fcurves = cons_obj.animation_data.action.fcurves
122
#Find the RNA data path of the constraint's influence
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.
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
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]
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)
170
xCurves.append(newCurve)
171
elif path in c_y_path:
172
yCurves.append(newCurve)
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:
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
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
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]
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
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())
262
#~ vec = obj.parent.matrix_world * vec
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
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))
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
335
real_constraint.mute = (not m_constraint.active)
338
def locBake(s_frame, e_frame, bones):
339
scene = bpy.context.scene
342
bakeDict[bone.name] = {}
343
for t in range(s_frame, e_frame):
346
bakeDict[bone.name][t] = bone.matrix.copy()
347
for t in range(s_frame, e_frame):
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):
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)
366
#If it's an auto generated IK:
367
if ik.chain_count == 0:
368
selectedBones += bones # Chain len 0, bake everything
370
selectedBones += [end_bone] + end_bone.parent_recursive[:ik.chain_count - 1] # Bake the chain
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
386
# Use bake function from NLA Bake Action operator
387
anim_utils.bake_action(s_frame,
389
action=constraintStrip.action,
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
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)
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)
441
m_constraint.active = True
444
def updateConstraints(obj, context):
445
fixes = obj.data.mocap_constraints