auto_walk/OP_expand_cycle_step.py

312 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import bpy
from . import fn
## step 3
# - Bake cycle modifier keys -chained with- step the animation path
# - Pin the feet (separated ops)
def bake_cycle(on_selection=True):
print(fn.helper())
obj = bpy.context.object
if obj.type != 'ARMATURE':
print('ERROR', 'active is not an armature type')
return
act = fn.set_expanded_action(obj)
if not act:
return
print('action', act.name)
act = obj.animation_data.action
ct = 0
for fcu in act.fcurves:
## if a curve is not cycled don't touch
if not [m for m in fcu.modifiers if m.type == 'CYCLES']:
continue
#-# only on location :
# if not fcu.data_path.endswith('.location'):
# continue
# prop = fcu.data_path.split('.')[-1]
b_name = fcu.data_path.split('"')[1]
pb = obj.pose.bones.get(b_name)
if not pb:
print(f'{b_name} is invalid')
continue
#-# limit on selection if passed
if on_selection and not pb.bone.select:
continue
#-# only on selected and visible curve
# if not fcu.select or fcu.hide:
# continue
fcu_kfs = []
for k in fcu.keyframe_points:
k_dic = {}
# k_dic['k'] = k
k_dic['co'] = k.co
k_dic['interpolation'] = k.interpolation
k_dic['type'] = k.type
fcu_kfs.append(k_dic)
first = fcu_kfs[0]['co'][0]
# second = fcu_kfs[1]['co'][0]
# before_last= fcu_kfs[-2]['co'][0]
last = fcu_kfs[-1]['co'][0]
# first_offset = second - first
current_offset = offset = last - first
print('offset: ', offset)
## expand to end frame
end = bpy.context.scene.frame_end # maybe add possibility define target manually
iterations = ((end - last) // offset) + 1
print('iterations: ', iterations)
for _i in range(int(iterations)):
for kf in fcu_kfs:
# create a new key, adding offset to keys
fcu.keyframe_points.add(1)
new = fcu.keyframe_points[-1]
for att, val in kf.items():
if att == 'co':
new.co = (val[0] + current_offset, val[1])
else:
setattr(new, att, val)
current_offset += offset
ct += 1
if not ct:
return ('ERROR', 'No fcurve treated (! action duplicated to _expand !)')
print('end of anim cycle keys baking')
# C.scene.frame_current = org_frame
# detect last key in contact
def step_path():
print(fn.helper())
obj = bpy.context.object
if obj.type != 'ARMATURE':
return ('ERROR', 'active is not an armature type')
# found curve through constraint
curve, const = fn.get_follow_curve_from_armature(obj)
if not const:
return ('ERROR', 'No constraints found')
act = fn.get_obj_action(obj)
if not act: return
# CHANGE - retiré le int de la frame
# keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points]
keyframes = [k.co[0] for fcu in act.fcurves for k in fcu.keyframe_points]
keyframes = list(set(keyframes))
curve = const.target
if not curve:
return ('ERROR', f'no target set for {curve.name}')
# get a new generated action for the curve
# Follow path animation is on the DATA of the fcurve
fact = fn.set_generated_action(curve.data)
if not fact:
return
t_fcu = False
for fcu in fact.fcurves:
## fcu data_path is just a string
if fcu.data_path == 'eval_time':
t_fcu = fcu
if not t_fcu:
return ('ERROR', f'no eval_time animation in {curve.name}')
timevalues = [t_fcu.evaluate(kf) for kf in keyframes]
for kf, value in zip(keyframes, timevalues):
## or use t_fcu.keyframe_points.add(len(kf))
curve.data.eval_time = value
curve.data.keyframe_insert('eval_time', frame=kf, options={'INSERTKEY_AVAILABLE'})
# ``INSERTKEY_NEEDED````INSERTKEY_AVAILABLE`` (only available channels)
## set all to constant
for k in t_fcu.keyframe_points:
k.interpolation = 'CONSTANT'
print('end of step_anim')
class UAC_OT_bake_cycle_and_step(bpy.types.Operator):
bl_idname = "anim.bake_cycle_and_step"
bl_label = "Bake key and step path "
bl_description = "Bake the key and step the animation path according to those key"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def execute(self, context):
err = bake_cycle(context.scene.anim_cycle_settings.expand_on_selected_bones)
if err:
self.report({err[0]}, err[1])
if err[0] == 'ERROR':
return {"CANCELLED"}
# CHAINED ACTION : step the path of the curve path
err = step_path()
if err:
self.report({err[0]}, err[1])
if err[0] == 'ERROR':
return {"CANCELLED"}
# CHAINED ACTION pin feet ?? : Step the path of the curve path
return {"FINISHED"}
def pin_down_feets():
print(fn.helper())
obj = bpy.context.object
if obj.type != 'ARMATURE':
print('ERROR', 'active is not an armature type')
return
# Delete current action if its not the main one
# create a new '_autogen' one
act = fn.set_generated_action(obj)
if not act:
return ('ERROR', f'No action on {obj.name}')
print('action', act.name)
# detect contact key
# [ 'array_index', 'auto_smoothing', 'bl_rna', 'color', 'color_mode', 'convert_to_keyframes', 'convert_to_samples', 'data_path',
# 'driver', 'evaluate', 'extrapolation', 'group', 'hide', 'is_empty', 'is_valid', 'keyframe_points', 'lock', 'modifiers', 'mute',
# 'range', 'rna_type', 'sampled_points', 'select', 'update', 'update_autoflags']
act = obj.animation_data.action
org_frame = bpy.context.scene.frame_current
# TODO autodetect contact frame ?
done = {}
for fcu in act.fcurves:
# check only location
if not fcu.data_path.endswith('.location'):
continue
# prop = fcu.data_path.split('.')[-1]
b_name = fcu.data_path.split('"')[1]
# print('b_name: ', b_name, fcu.is_valid)
pb = obj.pose.bones.get(b_name)
if not pb:
print(f'{b_name} is invalid')
continue
start_contact = None
for k in fcu.keyframe_points:
if k.type == 'EXTREME':
bpy.context.scene.frame_set(k.co[0])
if start_contact is None:
start_contact=k.co[0]
# record coordinate relative to referent object (or world coord)
bone_mat = pb.matrix.copy()
# bone_mat = obj.matrix_world @ pb.matrix.copy()
continue
if b_name in done.keys():
if k.co[0] in done[b_name]:
continue
else:
# mark as treated (all curve of this bone at this time)
done[b_name] = [k.co[0]]
#-# Insert keyframe to match Hold position
# print(f'Apply on {b_name} at {k.co[0]}')
#-# assign previous matrix
pbl = pb.location.copy()
# l, _r, _s = bone_mat.decompose()
pb.matrix = bone_mat # Exact same position
# pb.location.x = pbl.x # dont touch x either
pb.location.z = pbl.z
# pb.location.y = l.y (weirdly not working)
# bpy.context.view_layer.update()
#-# moyenne des 2 ?
# pb.location, pb.rotation_euler, pb.scale = average_two_matrix(pb.matrix, bone_mat) ## marche pas du tout !
## insert keyframe
pb.keyframe_insert('location')
# only touched Y location
# pb.keyframe_insert('rotation_euler')
k.type = 'JITTER' # 'BREAKDOWN' 'MOVING_HOLD' 'JITTER'
else:
if start_contact is not None:
# print('fcu.data_path: ', fcu.data_path, fcu.array_index)
# print(f'{b_name} contact range {start_contact} - {k.co[0]}')
start_contact = None
# print(i, fcu.data_path, fcu.array_index)
# print('time', k.co[0], '- value', k.co[1])
#k.handle_left
#k.handle_right
##change handler type ([FREE, VECTOR, ALIGNED, AUTO, AUTO_CLAMPED], default FREE)
#k.handle_left_type = 'AUTO_CLAMPED'
#k.handle_right_type = 'AUTO_CLAMPED'
bpy.context.scene.frame_current = org_frame
# detect last key in contact
class UAC_OT_pin_feets(bpy.types.Operator):
bl_idname = "anim.pin_feets"
bl_label = "Pin Feets"
bl_description = "Pin feets on keys marked as extreme"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def execute(self, context):
# context.scene.anim_cycle_settings.expand_on_selected_bones
err = pin_down_feets()
if err:
self.report({err[0]}, err[1])
if err[0] == 'ERROR':
return {"CANCELLED"}
return {"FINISHED"}
classes=(
UAC_OT_bake_cycle_and_step,
UAC_OT_pin_feets,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)