|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
import bpy
|
|
|
|
|
from . import fn
|
|
|
|
|
from mathutils import Vector, Euler
|
|
|
|
|
from mathutils.geometry import intersect_line_plane
|
|
|
|
|
## step 2 : Auto animate the path (with feet selection) and modal to adjust speed
|
|
|
|
|
|
|
|
|
|
def get_bone_transform_at_frame(b, act, frame):
|
|
|
|
@ -11,12 +12,12 @@ def get_bone_transform_at_frame(b, act, frame):
|
|
|
|
|
for i in range(3):
|
|
|
|
|
f = act.fcurves.find(f'pose.bones["{b.name}"].{channel}', index=i)
|
|
|
|
|
if not f:
|
|
|
|
|
print(frame, channel, 'not animated ! using current value')
|
|
|
|
|
# print(frame, channel, 'not animated ! using current value') # Dbg
|
|
|
|
|
chan_list.append(getattr(b, channel)) # get current value since not animated
|
|
|
|
|
continue
|
|
|
|
|
chan_list.append(f.evaluate(frame))
|
|
|
|
|
|
|
|
|
|
print(frame, b.name, channel, chan_list)
|
|
|
|
|
# print(frame, b.name, channel, chan_list) # Dbg
|
|
|
|
|
if channel == 'rotation_euler':
|
|
|
|
|
transform[channel] = Euler(chan_list)
|
|
|
|
|
else:
|
|
|
|
@ -26,10 +27,11 @@ def get_bone_transform_at_frame(b, act, frame):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def anim_path_from_translate():
|
|
|
|
|
'''Calculate step size from selected foot and forward axis and animate curve'''
|
|
|
|
|
print(fn.helper())
|
|
|
|
|
|
|
|
|
|
obj = bpy.context.object
|
|
|
|
|
if obj.type != 'ARMATURE':
|
|
|
|
|
ob = bpy.context.object
|
|
|
|
|
if ob.type != 'ARMATURE':
|
|
|
|
|
return ('ERROR', 'active is not an armature type')
|
|
|
|
|
|
|
|
|
|
# found curve through constraint
|
|
|
|
@ -37,20 +39,24 @@ def anim_path_from_translate():
|
|
|
|
|
|
|
|
|
|
# if not 'foot' in b.bone.name:
|
|
|
|
|
# return ('ERROR', 'No "foot" in active bone name\n-> Select foot that has the most reliable contact')
|
|
|
|
|
|
|
|
|
|
settings = bpy.context.scene.anim_cycle_settings
|
|
|
|
|
axis = settings.forward_axis
|
|
|
|
|
curve = None
|
|
|
|
|
if bpy.context.scene.anim_cycle_settings.path_to_follow:
|
|
|
|
|
curve = bpy.context.scene.anim_cycle_settings.path_to_follow
|
|
|
|
|
if settings.path_to_follow:
|
|
|
|
|
curve = settings.path_to_follow
|
|
|
|
|
|
|
|
|
|
if curve and not bpy.context.scene.objects.get(curve.name):
|
|
|
|
|
return 'ERROR', f'Curve {curve.name} is not in scene'
|
|
|
|
|
|
|
|
|
|
# if curve is not defined try to track it from constraints on armature
|
|
|
|
|
if not curve:
|
|
|
|
|
curve, _const = fn.get_follow_curve_from_armature(obj)
|
|
|
|
|
curve, _const = fn.get_follow_curve_from_armature(ob)
|
|
|
|
|
if isinstance(curve, str):
|
|
|
|
|
return curve, _const
|
|
|
|
|
|
|
|
|
|
act = fn.get_obj_action(obj)
|
|
|
|
|
act = fn.get_obj_action(ob)
|
|
|
|
|
if not act:
|
|
|
|
|
return ('ERROR', f'No action active on {obj.name}')
|
|
|
|
|
return ('ERROR', f'No action active on {ob.name}')
|
|
|
|
|
|
|
|
|
|
# use original action as ref
|
|
|
|
|
if '_expanded' in act.name:
|
|
|
|
@ -104,32 +110,91 @@ def anim_path_from_translate():
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if start_frame is None or end_frame is None:
|
|
|
|
|
return ('ERROR', f'No (or not enough) keyframe marked Extreme {obj.name} > {b.name}')
|
|
|
|
|
return ('ERROR', f'No (or not enough) keyframe marked Extreme {ob.name} > {b.name}')
|
|
|
|
|
if start_frame == end_frame:
|
|
|
|
|
return ('ERROR', f'Only one key detected as extreme (at frame {start_frame}) !\nNeed at least two chained marked keys')
|
|
|
|
|
|
|
|
|
|
print(f'Offset from key range. start: {start_frame} - end: {end_frame}')
|
|
|
|
|
move_frame = end_frame - start_frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Find move_val from diff position at start and end frame wihtin character forward axis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start_transform = get_bone_transform_at_frame(b, act, start_frame)
|
|
|
|
|
start_mat = fn.compose_matrix(start_transform['location'], start_transform['rotation_euler'], start_transform['scale'])
|
|
|
|
|
|
|
|
|
|
end_transform = get_bone_transform_at_frame(b, act, end_frame)
|
|
|
|
|
end_mat = fn.compose_matrix(end_transform['location'], end_transform['rotation_euler'], end_transform['scale'])
|
|
|
|
|
|
|
|
|
|
# bpy.context.scene.cursor.location = (b.bone.matrix_local @ start_mat).to_translation() # obj.matrix_world @
|
|
|
|
|
bpy.context.scene.cursor.location = (b.bone.matrix_local @ end_mat).to_translation() # obj.matrix_world @
|
|
|
|
|
# bpy.context.scene.cursor.location = (b.matrix_basis.inverted() @ start_mat).to_translation() # obj.matrix_world @
|
|
|
|
|
# bpy.context.scene.cursor.location = start_transform['location'] # obj.matrix_world @
|
|
|
|
|
return # FIXME
|
|
|
|
|
|
|
|
|
|
### old method using directly one axis
|
|
|
|
|
"""
|
|
|
|
|
## Determine direction vector of the charater (root)
|
|
|
|
|
orient_vectors = {
|
|
|
|
|
'FORWARD_X' : Vector((1,0,0)),
|
|
|
|
|
'FORWARD_Y' : Vector((0,1,0)),
|
|
|
|
|
'FORWARD_Z' : Vector((0,0,1)),
|
|
|
|
|
'TRACK_NEGATIVE_X' : Vector((-1,0,0)),
|
|
|
|
|
'TRACK_NEGATIVE_Y' : Vector((0,-1,0)),
|
|
|
|
|
'TRACK_NEGATIVE_Z' : Vector((0,0,-1))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
## TODO root need to be user defined
|
|
|
|
|
root = ob.pose.bones.get('world')
|
|
|
|
|
if not root:
|
|
|
|
|
print('No root found')
|
|
|
|
|
return {"CANCELLED"}
|
|
|
|
|
|
|
|
|
|
root_axis_vec = orient_vectors[axis] # world space
|
|
|
|
|
root_axis_vec = root.bone.matrix_local @ root_axis_vec # aligned with object
|
|
|
|
|
# bpy.context.scene.cursor.location = root_axis_vec # Dbg root direction
|
|
|
|
|
|
|
|
|
|
## Get difference between start_loc and ends loc on forward axis
|
|
|
|
|
start_loc = (b.bone.matrix_local @ start_mat).to_translation()
|
|
|
|
|
end_loc = (b.bone.matrix_local @ end_mat).to_translation()
|
|
|
|
|
# bpy.context.scene.cursor.location = start_loc # Dbg foot start position
|
|
|
|
|
|
|
|
|
|
print('root vec : ', root_axis_vec)
|
|
|
|
|
print('start loc: ', start_loc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## get distance on forward axis
|
|
|
|
|
move_val = (intersect_line_plane(start_loc, start_loc + root_axis_vec, end_loc, root_axis_vec) - start_loc).length
|
|
|
|
|
print('move_val: ', move_val)
|
|
|
|
|
|
|
|
|
|
length = fn.get_curve_length(curve)
|
|
|
|
|
|
|
|
|
|
steps = length / move_val
|
|
|
|
|
|
|
|
|
|
frame_duration = int(steps * move_frame)
|
|
|
|
|
|
|
|
|
|
## Clear 'eval_time' keyframe before creating new ones # curve.data.animation_data_clear() # too much.. delete only eval_time
|
|
|
|
|
if curve.data.animation_data and curve.data.animation_data.action:
|
|
|
|
|
for fcu in curve.data.animation_data.action.fcurves:
|
|
|
|
|
if fcu.data_path == 'eval_time':
|
|
|
|
|
curve.data.animation_data.action.fcurves.remove(fcu)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
## add eval time animation on curve
|
|
|
|
|
anim_frame = settings.start_frame
|
|
|
|
|
curve.data.path_duration = frame_duration
|
|
|
|
|
curve.data.eval_time = 0
|
|
|
|
|
curve.data.keyframe_insert('eval_time', frame=anim_frame) # , options={'INSERTKEY_AVAILABLE'}
|
|
|
|
|
|
|
|
|
|
curve.data.eval_time = frame_duration
|
|
|
|
|
curve.data.keyframe_insert('eval_time', frame=anim_frame + frame_duration)
|
|
|
|
|
|
|
|
|
|
## all to linear (will be set to CONSTANT at the moment of sampling)
|
|
|
|
|
for fcu in curve.data.animation_data.action.fcurves:
|
|
|
|
|
if fcu.data_path == 'eval_time':
|
|
|
|
|
for k in fcu.keyframe_points:
|
|
|
|
|
k.interpolation = 'LINEAR'
|
|
|
|
|
|
|
|
|
|
## set all to constant
|
|
|
|
|
# for k in t_fcu.keyframe_points:
|
|
|
|
|
# k.interpolation = 'CONSTANT'
|
|
|
|
|
|
|
|
|
|
print('end of set_follow_path_anim')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" ### old method using directly one axis
|
|
|
|
|
axis = {'X':0, 'Y':1, 'Z':2}
|
|
|
|
|
## calculate offset from bones
|
|
|
|
|
loc_fcu = None
|
|
|
|
@ -177,43 +242,6 @@ def anim_path_from_translate():
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print('move_val: ', move_val)
|
|
|
|
|
|
|
|
|
|
length = fn.get_curve_length(curve)
|
|
|
|
|
|
|
|
|
|
steps = length / move_val
|
|
|
|
|
|
|
|
|
|
frame_duration = steps * move_frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Clear keyframe before creating new ones
|
|
|
|
|
# curve.data.animation_data_clear() # too much.. delete only eval_time
|
|
|
|
|
if curve.data.animation_data and curve.data.animation_data.action:
|
|
|
|
|
for fcu in curve.data.animation_data.action.fcurves:
|
|
|
|
|
if fcu.data_path == 'eval_time':
|
|
|
|
|
curve.data.animation_data.action.fcurves.remove(fcu)
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
anim_frame = bpy.context.scene.anim_cycle_settings.start_frame
|
|
|
|
|
curve.data.path_duration = frame_duration
|
|
|
|
|
curve.data.eval_time = 0
|
|
|
|
|
curve.data.keyframe_insert('eval_time', frame=anim_frame)# , options={'INSERTKEY_AVAILABLE'}
|
|
|
|
|
|
|
|
|
|
curve.data.eval_time = frame_duration
|
|
|
|
|
curve.data.keyframe_insert('eval_time', frame=anim_frame + frame_duration)
|
|
|
|
|
|
|
|
|
|
## all to linear (will be set to CONSTANT at the moment of sampling)
|
|
|
|
|
for fcu in curve.data.animation_data.action.fcurves:
|
|
|
|
|
if fcu.data_path == 'eval_time':
|
|
|
|
|
for k in fcu.keyframe_points:
|
|
|
|
|
k.interpolation = 'LINEAR'
|
|
|
|
|
|
|
|
|
|
## set all to constant
|
|
|
|
|
# for k in t_fcu.keyframe_points:
|
|
|
|
|
# k.interpolation = 'CONSTANT'
|
|
|
|
|
|
|
|
|
|
print('end of set_follow_path_anim')
|
|
|
|
|
|
|
|
|
|
class UAC_OT_animate_path(bpy.types.Operator):
|
|
|
|
|
bl_idname = "anim.animate_path"
|
|
|
|
|
bl_label = "Animate Path"
|
|
|
|
|