improved cycle bake (still need cycle modifier)
1.4.0 - added: preview of end frame for the cycle bake - fixed: better method to detect key cycle rangemaster
parent
cd17ccf708
commit
8f1f1a6882
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
1.4.0
|
||||||
|
|
||||||
|
- added: preview of end frame for the cycle bake
|
||||||
|
- fixed: better method to detect key cycle range
|
||||||
|
|
||||||
1.3.4
|
1.3.4
|
||||||
|
|
||||||
- fixed: bake keys with better cycle compatibility
|
- fixed: bake keys with better cycle compatibility
|
||||||
|
|
|
@ -106,14 +106,7 @@ def find_best_foot(ob):
|
||||||
return ('ERROR', f'No action active on {ob.name}')
|
return ('ERROR', f'No action active on {ob.name}')
|
||||||
|
|
||||||
# use original action as ref
|
# use original action as ref
|
||||||
if '_baked' in act.name:
|
act = fn.get_origin_action(act)
|
||||||
base_act_name = act.name.split('_baked')[0]
|
|
||||||
base_act = bpy.data.actions.get(base_act_name)
|
|
||||||
if base_act:
|
|
||||||
act = base_act
|
|
||||||
print(f'Using for action {base_act_name} as reference')
|
|
||||||
else:
|
|
||||||
print(f'No base action found (searching for {base_act_name})')
|
|
||||||
|
|
||||||
if 'foot' in b.name.lower():
|
if 'foot' in b.name.lower():
|
||||||
# if best is selected
|
# if best is selected
|
||||||
|
|
|
@ -10,7 +10,7 @@ from time import time
|
||||||
def bake_cycle(on_selection=True, end=None):
|
def bake_cycle(on_selection=True, end=None):
|
||||||
print(fn.helper())
|
print(fn.helper())
|
||||||
end = end or bpy.context.scene.frame_end
|
end = end or bpy.context.scene.frame_end
|
||||||
end += 20 # add an hardcoded margin !
|
|
||||||
print('end: ', end)
|
print('end: ', end)
|
||||||
debug = fn.get_addon_prefs().debug
|
debug = fn.get_addon_prefs().debug
|
||||||
obj = bpy.context.object
|
obj = bpy.context.object
|
||||||
|
@ -22,7 +22,7 @@ def bake_cycle(on_selection=True, end=None):
|
||||||
if not act:
|
if not act:
|
||||||
return
|
return
|
||||||
|
|
||||||
if debug: print('action', act.name)
|
if debug: print('action:', act.name)
|
||||||
|
|
||||||
# obj.animation_data.action = act
|
# obj.animation_data.action = act
|
||||||
|
|
||||||
|
@ -30,10 +30,10 @@ def bake_cycle(on_selection=True, end=None):
|
||||||
ct = 0
|
ct = 0
|
||||||
ct_no_cycle = 0
|
ct_no_cycle = 0
|
||||||
|
|
||||||
## TODO calculate offset only once to avoid errors !
|
# all_keys = [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points]
|
||||||
all_keys = [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points]
|
all_keys = fn.get_x_pos_of_visible_keys(obj, act)
|
||||||
first = int(min(all_keys))
|
first = min(all_keys) # int(min(all_keys))
|
||||||
last = int(max(all_keys))
|
last = max(all_keys) # int(max(all_keys))
|
||||||
offset = last - first
|
offset = last - first
|
||||||
|
|
||||||
for fcu in act.fcurves:
|
for fcu in act.fcurves:
|
||||||
|
@ -134,9 +134,6 @@ def bake_cycle(on_selection=True, end=None):
|
||||||
setattr(new, att, val)
|
setattr(new, att, val)
|
||||||
current_offset += offset
|
current_offset += offset
|
||||||
|
|
||||||
# FIXME on last cycle - re-add last keyframe to "close" the cycle
|
|
||||||
|
|
||||||
|
|
||||||
ct += 1
|
ct += 1
|
||||||
|
|
||||||
if ct_fcu == ct_no_cycle: # skipped because no cycle exists
|
if ct_fcu == ct_no_cycle: # skipped because no cycle exists
|
||||||
|
@ -146,12 +143,12 @@ def bake_cycle(on_selection=True, end=None):
|
||||||
org_action_name = re_baked.sub('', act.name)
|
org_action_name = re_baked.sub('', act.name)
|
||||||
org_action = bpy.data.actions.get(org_action_name)
|
org_action = bpy.data.actions.get(org_action_name)
|
||||||
if not org_action:
|
if not org_action:
|
||||||
return ('ERROR', 'No fcurve with anim cycle found (on expanded action)')
|
return ('ERROR', 'No fcurve with anim cycle found (on baked action)')
|
||||||
obj.animation_data.action = org_action
|
obj.animation_data.action = org_action
|
||||||
return ('ERROR', 'No fcurve with anim cycle found (back to unexpanded)')
|
return ('ERROR', 'No fcurve with anim cycle found (back to unexpanded)')
|
||||||
|
|
||||||
if not ct:
|
if not ct:
|
||||||
return ('ERROR', 'No fcurve treated (! action duplicated to _expand !)')
|
return ('ERROR', 'No fcurve treated (! action duplicated to _baked !)')
|
||||||
|
|
||||||
# cleaning update
|
# cleaning update
|
||||||
fn.update_action(act)
|
fn.update_action(act)
|
||||||
|
@ -214,62 +211,6 @@ def step_path():
|
||||||
fn.update_action(act)
|
fn.update_action(act)
|
||||||
print('end of step_anim')
|
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')
|
|
||||||
|
|
||||||
# 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 - 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))
|
|
||||||
|
|
||||||
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'
|
|
||||||
|
|
||||||
# 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):
|
class AW_OT_bake_cycle_and_step(bpy.types.Operator):
|
||||||
bl_idname = "autowalk.bake_cycle_and_step"
|
bl_idname = "autowalk.bake_cycle_and_step"
|
||||||
bl_label = "Bake keys"
|
bl_label = "Bake keys"
|
||||||
|
@ -286,6 +227,17 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator):
|
||||||
|
|
||||||
def invoke(self,context,event):
|
def invoke(self,context,event):
|
||||||
self.end_frame = context.scene.frame_end
|
self.end_frame = context.scene.frame_end
|
||||||
|
act = fn.get_obj_action(context.object)
|
||||||
|
if not act:
|
||||||
|
self.report({'ERROR'}, 'No Animation set on active object')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
act = fn.get_origin_action(act)
|
||||||
|
# all_keys = [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points]
|
||||||
|
all_keys = fn.get_x_pos_of_visible_keys(context.object, act) # no offset and only visible bone layers
|
||||||
|
self.first = min(all_keys) # int(min(all_keys))
|
||||||
|
self.last = max(all_keys) # int(max(all_keys))
|
||||||
|
self.offset = self.last - self.first
|
||||||
# return self.execute(context) # uncomment only this to skip pop-up and keep scene.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
|
return context.window_manager.invoke_props_dialog(self) # width=400
|
||||||
|
|
||||||
|
@ -294,6 +246,9 @@ class AW_OT_bake_cycle_and_step(bpy.types.Operator):
|
||||||
layout.use_property_split = True
|
layout.use_property_split = True
|
||||||
layout.label(text='End of cycle duplication')
|
layout.label(text='End of cycle duplication')
|
||||||
layout.prop(self, 'end_frame', text='End Frame')
|
layout.prop(self, 'end_frame', text='End Frame')
|
||||||
|
iteration = ((self.end_frame - self.first) // self.offset) + 1
|
||||||
|
real_end_cycle = iteration * self.offset + self.first
|
||||||
|
layout.label(text=f'Cycle will stop at frame: {real_end_cycle}')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ bl_info = {
|
||||||
"name": "Auto Walk",
|
"name": "Auto Walk",
|
||||||
"description": "Develop a walk/run cycles along a curve and pin feets",
|
"description": "Develop a walk/run cycles along a curve and pin feets",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (1, 3, 4),
|
"version": (1, 4, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
52
fn.py
52
fn.py
|
@ -202,6 +202,17 @@ def set_baked_action(obj):
|
||||||
obj.animation_data.action = new_act
|
obj.animation_data.action = new_act
|
||||||
return new_act
|
return new_act
|
||||||
|
|
||||||
|
def get_origin_action(act):
|
||||||
|
'''Return original action if found, else return same'''
|
||||||
|
if '_baked' in act.name:
|
||||||
|
base_act_name = act.name.split('_baked')[0]
|
||||||
|
base_act = bpy.data.actions.get(base_act_name)
|
||||||
|
if base_act:
|
||||||
|
act = base_act
|
||||||
|
print(f'Using for action {base_act_name} as reference')
|
||||||
|
else:
|
||||||
|
print(f'No base action found (searching for {base_act_name})')
|
||||||
|
return act
|
||||||
|
|
||||||
def get_curve_length(ob):
|
def get_curve_length(ob):
|
||||||
'''Get a curve object, return a float representing world space length'''
|
'''Get a curve object, return a float representing world space length'''
|
||||||
|
@ -640,6 +651,9 @@ def create_cycle_modifiers(ob=None):
|
||||||
continue
|
continue
|
||||||
if not '"' in fc.data_path:
|
if not '"' in fc.data_path:
|
||||||
continue
|
continue
|
||||||
|
# skip offset
|
||||||
|
if fc.data_path.endswith('.offset') and 'constraint' in fc.data_path:
|
||||||
|
continue
|
||||||
b_name = fc.data_path.split('"')[1]
|
b_name = fc.data_path.split('"')[1]
|
||||||
if b_name.lower().startswith(('mch', 'def', 'org')):
|
if b_name.lower().startswith(('mch', 'def', 'org')):
|
||||||
continue
|
continue
|
||||||
|
@ -669,3 +683,41 @@ def go_edit_mode(ob, context=None):
|
||||||
ob.select_set(True)
|
ob.select_set(True)
|
||||||
context.view_layer.objects.active = ob
|
context.view_layer.objects.active = ob
|
||||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||||
|
|
||||||
|
|
||||||
|
def get_visible_bones(ob):
|
||||||
|
'''Get name of all editable bones (unhided *and* on a visible bone layer'''
|
||||||
|
# visible bone layer index
|
||||||
|
visible_layers_indexes = [i for i, l in enumerate(ob.data.layers) if l]
|
||||||
|
# check if layers overlaps
|
||||||
|
visible_bones = [b for b in ob.data.bones \
|
||||||
|
if not b.hide \
|
||||||
|
if any(i for i, l in enumerate(b.layers) if l and i in visible_layers_indexes)]
|
||||||
|
|
||||||
|
return visible_bones
|
||||||
|
|
||||||
|
|
||||||
|
def get_x_pos_of_visible_keys(ob, act):
|
||||||
|
'''Get an object and associated action
|
||||||
|
return x.coordinate of all fcurves.keys of all visible bones
|
||||||
|
'''
|
||||||
|
|
||||||
|
## just skip offset
|
||||||
|
# return [k.co.x for fc in act.fcurves if not '.offset' in fc.data_path for k in fc.keyframe_points]
|
||||||
|
|
||||||
|
## skip offset + fcu related to invisible bones
|
||||||
|
viz_bones = get_visible_bones(ob)
|
||||||
|
visible_bone_names = [b.name for b in viz_bones]
|
||||||
|
|
||||||
|
keys = []
|
||||||
|
for fc in act.fcurves:
|
||||||
|
if '.offset' in fc.data_path:
|
||||||
|
continue
|
||||||
|
if not '"' in fc.data_path:
|
||||||
|
continue
|
||||||
|
if not fc.data_path.split('"')[1] in visible_bone_names:
|
||||||
|
continue
|
||||||
|
keys += [k.co.x for k in fc.keyframe_points]
|
||||||
|
|
||||||
|
return keys
|
||||||
|
|
Loading…
Reference in New Issue