244 lines
8.1 KiB
Python
244 lines
8.1 KiB
Python
import bpy
|
|
from . import fn
|
|
|
|
## step 2 : Auto animate the path (with feet selection) and modal to adjust speed
|
|
|
|
def anim_path_from_y_translate():
|
|
print(fn.helper())
|
|
|
|
obj = bpy.context.object
|
|
if obj.type != 'ARMATURE':
|
|
return ('ERROR', 'active is not an armature type')
|
|
|
|
# found curve through constraint
|
|
b = bpy.context.active_pose_bone
|
|
|
|
if not 'shoe' in b.bone.name:
|
|
return ('ERROR', 'No "shoe" in active bone name\n-> Select foot that has the most reliable contact')
|
|
|
|
curve = None
|
|
if bpy.context.scene.anim_cycle_settings.path_to_follow:
|
|
curve = bpy.context.scene.anim_cycle_settings.path_to_follow
|
|
|
|
# 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)
|
|
if isinstance(curve, str):
|
|
return curve, _const
|
|
|
|
act = fn.get_obj_action(obj)
|
|
if not act:
|
|
return ('ERROR', f'No action active on {obj.name}')
|
|
|
|
# use original action as ref
|
|
if '_expanded' in act.name:
|
|
base_act_name = act.name.split('_expanded')[0]
|
|
base_act = bpy.data.action.get(base_act_name)
|
|
if base_act:
|
|
act = base_act
|
|
print(f'Using for {base_act_name} as reference')
|
|
else:
|
|
print(f'No base action found (searching for {base_act_name})')
|
|
|
|
# CHANGE - retiré le int de la frame
|
|
# keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points]
|
|
|
|
|
|
|
|
## calculate offset from bones
|
|
locy_fcu = None
|
|
for fcu in act.fcurves:
|
|
if fcu.data_path.split('"')[1] != b.bone.name:
|
|
continue
|
|
if fcu.data_path.split('.')[-1] == 'location' and fcu.array_index == 1:
|
|
locy_fcu = fcu
|
|
|
|
if not locy_fcu:
|
|
return ('ERROR', 'Current bone, location.y animation not found')
|
|
|
|
start = None
|
|
end = None
|
|
|
|
## based on extreme
|
|
for k in locy_fcu.keyframe_points:
|
|
# if k.select_control_point: # based on selection
|
|
if k.type == 'EXTREME': # using extreme keys.
|
|
if not start:
|
|
start = k
|
|
end = k
|
|
else:
|
|
if start is not None:
|
|
## means back to other frame type after passed breaskdown we stop
|
|
break
|
|
|
|
if not start:
|
|
return ('ERROR', f"No extreme marked frame was found on bone {b.bone.name}.{['x','y','z'][locy_fcu.array_index]}")
|
|
if start == end:
|
|
return ('ERROR', 'Seems like only one key was marked as extreme ! Need at least two chained')
|
|
|
|
start_frame = start.co.x
|
|
start_val = start.co.y
|
|
|
|
end_frame = end.co.x
|
|
end_val = end.co.y
|
|
|
|
move_frame = end_frame - start_frame
|
|
|
|
# Y positive value (forward) ->
|
|
move_val = abs(start_val - end_val)
|
|
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"
|
|
bl_description = "Select the most representative 'in contact' feet of the cycle"
|
|
bl_options = {"REGISTER", "UNDO"}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type == 'ARMATURE'
|
|
|
|
# def invoke(self, context, event):
|
|
# self.shift = event.shift
|
|
# return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
# TODO clear previous animation (keys) if there is any
|
|
err = anim_path_from_y_translate()
|
|
if err:
|
|
self.report({err[0]}, err[1])
|
|
if err[0] == 'ERROR':
|
|
return {"CANCELLED"}
|
|
return {"FINISHED"}
|
|
|
|
|
|
class UAC_OT_adjust_animation_length(bpy.types.Operator):
|
|
bl_idname = "anim.adjust_animation_length"
|
|
bl_label = "Adjust Anim speed"
|
|
bl_description = "Adjust speed\nOnce pressed, move up/down to move animation path last key value"
|
|
bl_options = {"REGISTER"} # , "UNDO"
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.object and context.object.type in ('ARMATURE', 'CURVE')
|
|
|
|
val : bpy.props.FloatProperty(name='End key value')
|
|
|
|
def invoke(self, context, event):
|
|
# check animation data of curve
|
|
# self.pref = fn.get_addon_prefs()
|
|
curve = bpy.context.scene.anim_cycle_settings.path_to_follow
|
|
if not curve:
|
|
if context.object.type != 'ARMATURE':
|
|
self.report({'ERROR'}, 'no curve targeted in "Path" field')
|
|
return {"CANCELLED"}
|
|
|
|
curve, _const = fn.get_follow_curve_from_armature(context.object)
|
|
if isinstance(curve, str):
|
|
self.report({curve}, _const)
|
|
return {"CANCELLED"}
|
|
|
|
self.act = fn.get_obj_action(curve.data)
|
|
if not self.act:
|
|
self.report({'ERROR'}, f'No action on {curve.name} data')
|
|
return {"CANCELLED"}
|
|
|
|
# if '_expanded' in self.act.name:
|
|
# self.report({'WARNING'}, f'Action is expanded')
|
|
|
|
self.fcu = None
|
|
for fcu in self.act.fcurves:
|
|
if fcu.data_path == 'eval_time':
|
|
self.fcu = fcu
|
|
break
|
|
|
|
if not self.fcu or not len(self.fcu.keyframe_points):
|
|
self.report({'ERROR'}, f'No eval_time animated on {curve.name} data action (or no keys)')
|
|
return {"CANCELLED"}
|
|
|
|
if len(self.fcu.keyframe_points) > 2:
|
|
self.report({'WARNING'}, f'{curve.name} eval_time has {len(self.fcu.keyframe_points)} keyframe (should just have 2 to redefine speed)')
|
|
|
|
self.k = self.fcu.keyframe_points[-1]
|
|
self.val = self.init_ky = self.k.co.y
|
|
self.init_my = event.mouse_y
|
|
|
|
context.window_manager.modal_handler_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
def modal(self, context, event):
|
|
# added reduction factor
|
|
self.val = self.init_ky + ((event.mouse_y - self.init_my) * 0.1)
|
|
offset = self.val - self.init_ky
|
|
display_text = f"Path animation end key value {self.val:.3f}, offset {offset:.3f}"
|
|
context.area.header_text_set(display_text)
|
|
self.k.co.y = self.val
|
|
if event.type == 'LEFTMOUSE' and event.value == "PRESS":
|
|
context.area.header_text_set(None)
|
|
self.execute(context)
|
|
return {"FINISHED"}
|
|
|
|
if event.type in ('RIGHTMOUSE', 'ESC') and event.value == "PRESS":
|
|
self.k.co.y = self.init_ky
|
|
context.area.header_text_set(None)
|
|
return {"CANCELLED"}
|
|
if event.type in ('MIDDLEMOUSE', 'SPACE'): # Mmaybe not mid mouse ?
|
|
return {'PASS_THROUGH'}
|
|
return {"RUNNING_MODAL"}
|
|
|
|
def execute(self, context):
|
|
self.k.co.y = self.val
|
|
return {"FINISHED"}
|
|
|
|
# def draw(self, context):
|
|
# layout = self.layout
|
|
# layout.prop(self, "val")
|
|
|
|
classes=(
|
|
UAC_OT_animate_path,
|
|
UAC_OT_adjust_animation_length,
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |