add anim strip key resync fix baking and step mode
1.3.3 - changed: `Set Time Keys` in NLA do not remove keys if exists but offset to match start of strip (if has moved) to resync - added: button to remove animated Strip time (delete keys but not fcurve) - fixed: step mode for bakingmaster
parent
072a483188
commit
d4ba199b63
|
@ -1,5 +1,11 @@
|
|||
# Changelog
|
||||
|
||||
1.3.3
|
||||
|
||||
- changed: `Set Time Keys` in NLA do not remove keys if exists but offset to match start of strip (if has moved) to resync
|
||||
- added: button to remove animated Strip time (delete keys but not fcurve)
|
||||
- fixed: step mode for baking
|
||||
|
||||
1.3.2
|
||||
|
||||
- removed: ground feet (added initial support for override compatibility wip)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from doctest import SKIP
|
||||
import bpy, re
|
||||
from . import fn
|
||||
from time import time
|
||||
|
@ -6,8 +7,9 @@ from time import time
|
|||
# - Bake cycle modifier keys -chained with- step the animation path
|
||||
# - Pin the feet (separated ops)
|
||||
|
||||
def bake_cycle(on_selection=True):
|
||||
def bake_cycle(on_selection=True, end=None):
|
||||
print(fn.helper())
|
||||
end = end or bpy.context.scene.frame_end
|
||||
debug = fn.get_addon_prefs().debug
|
||||
obj = bpy.context.object
|
||||
if obj.type != 'ARMATURE':
|
||||
|
@ -90,7 +92,8 @@ def bake_cycle(on_selection=True):
|
|||
## expand to end frame
|
||||
|
||||
# maybe add possibility define target manually ?
|
||||
end = bpy.context.scene.frame_end + 10 # add a margin
|
||||
|
||||
end += 20 # add an hardcoded margin !
|
||||
iterations = ((end - last) // offset) + 1
|
||||
if debug >= 2: print('iterations: ', iterations)
|
||||
for _i in range(int(iterations)):
|
||||
|
@ -108,10 +111,10 @@ def bake_cycle(on_selection=True):
|
|||
ct += 1
|
||||
|
||||
if ct_fcu == ct_no_cycle: # skipped because no cycle exists
|
||||
rexpand = re.compile(r'_baked\.?\d{0,3}$')
|
||||
if rexpand.search(act.name):
|
||||
re_baked = re.compile(r'_baked\.?\d{0,3}$')
|
||||
if re_baked.search(act.name):
|
||||
# is an autogenerated one
|
||||
org_action_name = rexpand.sub('', act.name)
|
||||
org_action_name = re_baked.sub('', act.name)
|
||||
org_action = bpy.data.actions.get(org_action_name)
|
||||
if not org_action:
|
||||
return ('ERROR', 'No fcurve with anim cycle found (on expanded action)')
|
||||
|
@ -125,12 +128,67 @@ def bake_cycle(on_selection=True):
|
|||
fn.update_action(act)
|
||||
print('end of anim cycle keys baking')
|
||||
# C.scene.frame_current = org_frame
|
||||
# detect last key in contact
|
||||
# detect last key in contact
|
||||
|
||||
def step_path():
|
||||
'''Step the path anim of the curve to constant'''
|
||||
print(fn.helper())
|
||||
|
||||
ob = bpy.context.object
|
||||
if ob.type != 'ARMATURE':
|
||||
return ('ERROR', 'active is not an armature type')
|
||||
|
||||
## found curve through constraint
|
||||
# curve, const = fn.get_follow_curve_from_armature(ob)
|
||||
# if not const:
|
||||
# return ('ERROR', 'No constraints found')
|
||||
|
||||
act = fn.get_obj_action(ob)
|
||||
if not act:
|
||||
return
|
||||
|
||||
# CHANGE - removed int from 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))
|
||||
|
||||
## get constraint
|
||||
# curve = const.target
|
||||
# if not curve:
|
||||
# return ('ERROR', f'no target set for {curve.name}')
|
||||
|
||||
offset_fc = None
|
||||
for fc in act.fcurves:
|
||||
if all(x in fc.data_path for x in ('pose.bones', 'constraints', 'offset')):
|
||||
offset_fc = fc
|
||||
|
||||
if not offset_fc:
|
||||
return ('ERROR', f'no offset animation in action {act.name}')
|
||||
|
||||
data_path = offset_fc.data_path
|
||||
const = ob.path_resolve(data_path.rsplit('.', 1)[0])
|
||||
|
||||
timevalues = [offset_fc.evaluate(kf) for kf in keyframes]
|
||||
for kf, value in zip(keyframes, timevalues):
|
||||
## or use t_fcu.keyframe_points.add(len(kf))
|
||||
|
||||
|
||||
const.offset = value
|
||||
const.keyframe_insert('offset', frame=kf, options={'INSERTKEY_AVAILABLE'})
|
||||
# ``INSERTKEY_NEEDED````INSERTKEY_AVAILABLE`` (only available channels)
|
||||
|
||||
## set all to constant
|
||||
for k in offset_fc.keyframe_points:
|
||||
k.interpolation = 'CONSTANT'
|
||||
|
||||
# cleaning update (might not be needed here)
|
||||
fn.update_action(act)
|
||||
print('end of step_anim')
|
||||
|
||||
'''
|
||||
def step_path():
|
||||
print(fn.helper())
|
||||
|
||||
obj = bpy.context.object
|
||||
if obj.type != 'ARMATURE':
|
||||
return ('ERROR', 'active is not an armature type')
|
||||
|
@ -141,7 +199,8 @@ def step_path():
|
|||
return ('ERROR', 'No constraints found')
|
||||
|
||||
act = fn.get_obj_action(obj)
|
||||
if not act: return
|
||||
if not act:
|
||||
return
|
||||
|
||||
# CHANGE - removed int from frame
|
||||
# keyframes = [int(k.co[0]) for fcu in act.fcurves for k in fcu.keyframe_points]
|
||||
|
@ -181,11 +240,12 @@ def step_path():
|
|||
# cleaning update (might not be needed here)
|
||||
fn.update_action(act)
|
||||
print('end of step_anim')
|
||||
|
||||
'''
|
||||
class AW_OT_bake_cycle_and_step(bpy.types.Operator):
|
||||
bl_idname = "autowalk.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_label = "Bake keys"
|
||||
bl_description = "Bake the keys to a new baked animation\
|
||||
\nStep path animation according to those key (if not in Linear)\
|
||||
\n(duplicate to a new 'baked' action)"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
|
@ -193,9 +253,22 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator):
|
|||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
end_frame : bpy.props.IntProperty(name='End Frame',options={'SKIP_SAVE'})
|
||||
|
||||
def invoke(self,context,event):
|
||||
self.end_frame = context.scene.frame_end
|
||||
# return self.execute(context) # uncomment only this to skip pop-up and keep scene.end
|
||||
return context.window_manager.invoke_props_dialog(self) # width=400
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
layout.label(text='End of cycle duplication')
|
||||
layout.prop(self, 'end_frame', text='End Frame')
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
err = bake_cycle(context.scene.anim_cycle_settings.expand_on_selected_bones)
|
||||
err = bake_cycle(context.scene.anim_cycle_settings.expand_on_selected_bones, end=self.endframe)
|
||||
if err:
|
||||
self.report({err[0]}, err[1])
|
||||
if err[0] == 'ERROR':
|
||||
|
|
|
@ -33,7 +33,83 @@ class AW_OT_nla_key_speed(bpy.types.Operator):
|
|||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
nla_strip = get_active_nla_strip()
|
||||
nla_strip = fn.get_nla_strip(context.object) # get_active_nla_strip()
|
||||
if not nla_strip:
|
||||
self.report({'ERROR'}, 'no active NLA strip')
|
||||
return {"CANCELLED"}
|
||||
|
||||
fcu = nla_strip.fcurves.find('strip_time')
|
||||
# if fcu:
|
||||
# for k in reversed(fcu.keyframe_points):
|
||||
# fcu.keyframe_points.remove(k)
|
||||
|
||||
nla_strip.use_animated_time = True
|
||||
if not fcu or len(fcu.keyframe_points) == 0:
|
||||
# create if not exists
|
||||
nla_strip.strip_time = nla_strip.action_frame_start
|
||||
nla_strip.keyframe_insert('strip_time', frame=nla_strip.frame_start)
|
||||
|
||||
nla_strip.strip_time = nla_strip.action_frame_end
|
||||
nla_strip.keyframe_insert('strip_time', frame=nla_strip.frame_end)
|
||||
|
||||
fcu = nla_strip.fcurves.find('strip_time')
|
||||
|
||||
# Go linear
|
||||
for k in fcu.keyframe_points:
|
||||
k.interpolation = 'LINEAR'
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
## if already exists : match offset (usefull when moving a strip)
|
||||
first = min([k.co.x for k in fcu.keyframe_points])
|
||||
|
||||
offset = nla_strip.frame_start - first
|
||||
|
||||
for k in fcu.keyframe_points:
|
||||
k.co.x += offset
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
class AW_OT_nla_remove_key_speed(bpy.types.Operator):
|
||||
bl_idname = "autowalk.nla_remove_key_speed"
|
||||
bl_label = "NLA Remove Key Speed"
|
||||
bl_description = "Remove strip time animation on active nla strip"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
nla_strip = fn.get_nla_strip(context.object) # get_active_nla_strip()
|
||||
if not nla_strip:
|
||||
self.report({'ERROR'}, 'no active NLA strip')
|
||||
return {"CANCELLED"}
|
||||
|
||||
fcu = nla_strip.fcurves.find('strip_time')
|
||||
if fcu:
|
||||
for k in reversed(fcu.keyframe_points):
|
||||
fcu.keyframe_points.remove(k)
|
||||
nla_strip.use_animated_time = False
|
||||
else:
|
||||
self.report({'ERROR'}, f'No strip time animation on active strip {nla_strip.name}')
|
||||
return {"CANCELLED"}
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
'''
|
||||
class AW_OT_nla_reset_key_speed(bpy.types.Operator):
|
||||
bl_idname = "autowalk.nla_reset_key_speed"
|
||||
bl_label = "NLA Reset Key Speed"
|
||||
bl_description = "Activate animate strip time and Keyframe linear for first and last animation frame"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object and context.object.type == 'ARMATURE'
|
||||
|
||||
def execute(self, context):
|
||||
nla_strip = fn.get_nla_strip(context.object) # get_active_nla_strip()
|
||||
if not nla_strip:
|
||||
self.report({'ERROR'}, 'no active NLA strip')
|
||||
return {"CANCELLED"}
|
||||
|
@ -60,9 +136,12 @@ class AW_OT_nla_key_speed(bpy.types.Operator):
|
|||
k.interpolation = 'LINEAR'
|
||||
|
||||
return {"FINISHED"}
|
||||
'''
|
||||
|
||||
classes=(
|
||||
AW_OT_nla_key_speed,
|
||||
AW_OT_nla_remove_key_speed,
|
||||
# AW_OT_nla_reset_key_speed,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
40
fn.py
40
fn.py
|
@ -105,17 +105,41 @@ def get_follow_curve_from_armature(arm):
|
|||
|
||||
# --- ACTIONS
|
||||
|
||||
def get_obj_action(obj):
|
||||
def get_nla_strip(ob):
|
||||
# get all strips in all tracks (can only be one active)
|
||||
strips = [s for t in ob.animation_data.nla_tracks for s in t.strips]
|
||||
if len(strips) == 1:
|
||||
return strips[0].action
|
||||
|
||||
if len(strips) > 1:
|
||||
# return active strip
|
||||
for s in strips:
|
||||
if s.active:
|
||||
return s.action
|
||||
|
||||
def get_obj_action(ob):
|
||||
print(helper())
|
||||
act = obj.animation_data
|
||||
act = ob.animation_data
|
||||
if not act:
|
||||
print('ERROR', f'no animation data on {obj.name}')
|
||||
print('ERROR', f'no animation data on {ob.name}')
|
||||
return
|
||||
|
||||
act = act.action
|
||||
if not act:
|
||||
print('ERROR', f'no action on {obj.name}')
|
||||
# check NLA
|
||||
strip = get_nla_strip(ob)
|
||||
|
||||
if strip:
|
||||
return strip
|
||||
|
||||
# there are multiple strips but no active
|
||||
if len([s for t in ob.animation_data.nla_tracks for s in t.strips]):
|
||||
print('ERROR', f'no active strip on NLA for {ob.name}')
|
||||
return
|
||||
|
||||
print('ERROR', f'no action on {ob.name}')
|
||||
return
|
||||
|
||||
return act
|
||||
|
||||
def set_generated_action(obj):
|
||||
|
@ -155,12 +179,12 @@ def set_baked_action(obj):
|
|||
'''
|
||||
print(helper())
|
||||
|
||||
rexpand = re.compile(r'_baked\.?\d{0,3}$')
|
||||
re_baked = re.compile(r'_baked\.?\d{0,3}$')
|
||||
act = obj.animation_data.action
|
||||
|
||||
if rexpand.search(act.name):
|
||||
if re_baked.search(act.name):
|
||||
# is an autogenerated one
|
||||
org_action_name = rexpand.sub('', act.name)
|
||||
org_action_name = re_baked.sub('', act.name)
|
||||
org_action = bpy.data.actions.get(org_action_name)
|
||||
if not org_action:
|
||||
print('ERROR', f'{org_action_name} not found')
|
||||
|
@ -439,7 +463,7 @@ def update_action(act):
|
|||
# update fcurves
|
||||
for fcu in act.fcurves:
|
||||
fcu.update()
|
||||
|
||||
|
||||
# redraw graph area
|
||||
for window in bpy.context.window_manager.windows:
|
||||
screen = window.screen
|
||||
|
|
|
@ -166,7 +166,9 @@ class AW_PT_nla_tools_panel(bpy.types.Panel):
|
|||
def draw(self, context):
|
||||
layout = self.layout
|
||||
# layout.label(text='Retime Tools')
|
||||
layout.operator('autowalk.nla_key_speed', text='Set Time Keys', icon='TIME')
|
||||
row = layout.row(align=True)
|
||||
row.operator('autowalk.nla_key_speed', text='Set/Update Time Keys', icon='TIME')
|
||||
row.operator('autowalk.nla_remove_key_speed', text='', icon='X')
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue