action swap and improved object / curve switch

0.8.0

- Easy jump to previous action
- improved armature <-> curve back and forth
- better UI
master
Pullusb 2022-04-12 19:24:47 +02:00
parent 427a3ad4b2
commit b983183504
5 changed files with 134 additions and 39 deletions

View File

@ -1,5 +1,11 @@
# Changelog
0.8.0
- Easy jump to previous action
- improved armature <-> curve back and forth
- better UI
0.7.1
- customizable panel category name

View File

@ -388,8 +388,6 @@ def pin_down_feets():
tmp_col.objects.unlink(obj)
bpy.data.collections.remove(tmp_col)
class UAC_OT_pin_feets(bpy.types.Operator):
bl_idname = "anim.pin_feets"
@ -411,9 +409,73 @@ class UAC_OT_pin_feets(bpy.types.Operator):
return {"FINISHED"}
class UAC_OT_set_action(bpy.types.Operator):
bl_idname = "uac.set_action"
bl_label = "Set action by name"
bl_description = "Set action on active object using passed name"
bl_options = {"REGISTER", "INTERNAL"}
act_name : bpy.props.StringProperty(options={'SKIP_SAVE'})
def execute(self, context):
act = bpy.data.actions.get(self.act_name)
if not act:
self.report({'ERROR'}, f'Could not find action {self.act_name} in bpy.data.actions')
return {"CANCELLED"}
context.object.animation_data.action = act
return {"FINISHED"}
class UAC_OT_step_back_actions(bpy.types.Operator):
bl_idname = "uac.step_back_actions"
bl_label = "Actions Step Back"
bl_description = "Step back to a previous action when 'baked' or 'pinned' action are not ok"
bl_options = {"REGISTER", "INTERNAL", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'ARMATURE'
def invoke(self, context, event):
act = context.object.animation_data.action
base_name = act.name.replace('_baked', '').replace('_pinned', '')
base_name = re.sub(r'\.\d{3}', '', base_name) # remove duplicate to search everything that has the same base
# self.actions = [a for a in bpy.data.actions if a.name.startswith(base_name) and not a.name == act.name] # skip current action
self.actions = [a for a in bpy.data.actions if a.name.startswith(base_name)]
if not len(self.actions):
self.report({'ERROR'}, f'no other action found for {act.name}\nUsing basename{base_name}')
return {'CANCELLED'}
if len(self.actions) == 1:
context.object.animation_data.action = self.actions[0]
return self.execute(context)
self.actions.sort(key=lambda x: len(x.name))
return context.window_manager.invoke_props_popup(self, event)
def draw(self, context):
layout = self.layout
# layout.label(text=f'Current Action: {context.object.animation_data.action.name}')
layout.label(text='Actions with same name base:')
for a in self.actions:
if a == context.object.animation_data.action:
layout.label(text=f'(current) >> {a.name}', icon='ACTION')
continue
layout.operator('UAC_OT_set_action', text=a.name, icon='ACTION').act_name = a.name
def execute(self, context):
return {"FINISHED"}
classes=(
UAC_OT_bake_cycle_and_step,
UAC_OT_pin_feets,
UAC_OT_set_action,
UAC_OT_step_back_actions,
)
def register():

View File

@ -226,18 +226,33 @@ class UAC_OT_edit_curve(bpy.types.Operator):
context.view_layer.objects.active = curve
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # EDIT_CURVE
b.id_data.select_set(False)
return {"FINISHED"}
class UAC_OT_set_choice_id(bpy.types.Operator):
bl_idname = "uac.set_choice_id"
bl_label = "Chosen ID"
bl_description = "Set passed id to a custom prop in window manager"
class UAC_OT_go_to_object(bpy.types.Operator):
bl_idname = "uac.go_to_object"
bl_label = "Go To Object"
bl_description = "Go to object in pose mode"
bl_options = {"REGISTER", "INTERNAL"}
idx : bpy.props.IntProperty(default=0, options={'SKIP_SAVE'})
obj_name : bpy.props.StringProperty(options={'SKIP_SAVE'})
def execute(self, context):
context.window_manager['back_to_armature_idx_prop'] = self.idx
obj = context.scene.objects.get(self.obj_name)
if not obj:
self.report({'ERROR'}, f'Could not find object {self.obj_name} in scene objects')
return {"CANCELLED"}
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
## deselect all armatures (or all objects)
for ob in context.scene.objects:
if ob.type == 'ARMATURE':
ob.select_set(False)
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='POSE', toggle=False)
self.report({'INFO'}, f'Back to pose mode {obj.name}')
return {"FINISHED"}
class UAC_OT_object_from_curve(bpy.types.Operator):
@ -266,31 +281,29 @@ class UAC_OT_object_from_curve(bpy.types.Operator):
self.report({'ERROR'}, 'No armature using this curve found')
return {"CANCELLED"}
curve.select_set(False)
if len(self.armatures) > 1:
# context.window_manager['back_to_armature_idx_prop']
# return context.window_manager.invoke_props_dialog(self, width=450) # execute on ok
return context.window_manager.invoke_props_popup(self, event) # execute on change
# set pose mode on only object available
obj, pb = self.armatures[0]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
for ob in context.scene.objects:
if ob.type == 'ARMATURE':
ob.select_set(False)
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='POSE', toggle=False)
self.report({'INFO'}, f'Back to pose mode {obj.name} (constraint on {pb.name})')
return self.execute(context)
def draw(self, context):
layout = self.layout
for i, item in enumerate(self.armatures):
arm, pb = item
layout.operator('uac.set_choice_id', text=f'{arm.name} > {pb.name}', icon='OUTLINER_OB_ARMATURE').idx = i
for arm, pb in self.armatures:
layout.operator('uac.go_to_object', text=f'{arm.name} ({pb.name})', icon='OUTLINER_OB_ARMATURE').obj_name = arm.name
def execute(self, context):
if len(self.armatures) > 1:
# use user chosen index
obj, pb = self.armatures[context.window_manager['back_to_armature_idx_prop']]
else:
obj, pb = self.armatures[0]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode='POSE', toggle=False)
self.report({'INFO'}, f'Back to pose mode {obj.name} (constraint on {pb.name})')
return {"FINISHED"}
classes=(
@ -298,7 +311,7 @@ UAC_OT_create_curve_path,
UAC_OT_create_follow_path,
UAC_OT_snap_curve_to_ground,
UAC_OT_edit_curve,
UAC_OT_set_choice_id,
UAC_OT_go_to_object,
UAC_OT_object_from_curve, # use set_choice_id is used to set an index in object_from_curve pop up menu
)

View File

@ -4,7 +4,7 @@ bl_info = {
"name": "Unfold Anim Cycle",
"description": "Anim tools to develop walk/run cycles along a curve",
"author": "Samuel Bernou",
"version": (0, 7, 1),
"version": (0, 8, 0),
"blender": (3, 0, 0),
"location": "View3D",
"warning": "WIP",

View File

@ -23,10 +23,27 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
row.label(text='Forward Axis')
row.prop(settings, "forward_axis", text='')
layout.operator("uac.autoset_axis", text='Auto-Set Axis')
pb = None
constrained = False
if ob and ob.type == 'ARMATURE':
pb = ob.pose.bones.get(prefs.tgt_bone)
if pb:
follow = pb.constraints.get('Follow Path')
if follow and follow.target:
constrained = True
if not settings.path_to_follow and not constrained:
layout.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
else:
layout.operator('uac.edit_curve', text='Edit Curve', icon='OUTLINER_DATA_CURVE') # FORCE_CURVE
if not settings.path_to_follow:
layout.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
elif ob and ob.type == 'CURVE':
if context.mode in ('OBJECT', 'EDIT_CURVE') \
and settings.path_to_follow \
and ob == settings.path_to_follow:
layout.operator('uac.object_from_curve', text='Back To Object', icon='LOOP_BACK')
box = layout.box()
expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT'
box.prop(settings, 'tweak', text='Curve Options', icon=expand_icon)
@ -41,7 +58,7 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
# Determine if already has a constraint (a bit too much condition in a panel...)
constrained = False
if ob:
if ob.type == 'ARMATURE':
pb = ob.pose.bones.get(prefs.tgt_bone)
@ -54,13 +71,7 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
# if context.mode == 'POSE':
if not constrained:
box.operator('anim.create_follow_path', text='Add follow path constraint', icon='CON_FOLLOWPATH')
box.operator('uac.edit_curve', text='Edit Curve', icon='OUTLINER_DATA_CURVE') # FORCE_CURVE
elif ob.type == 'CURVE':
if context.mode in ('OBJECT', 'EDIT_CURVE') \
and settings.path_to_follow \
and ob == settings.path_to_follow:
box.operator('uac.object_from_curve', text='Back To Object', icon='LOOP_BACK')
box = layout.box()
col=box.column()
@ -75,7 +86,7 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
## Bake cycle (on selected)
box = layout.box()
col=box.column()
col.label(text='Keys:')
col.label(text='Actions:')
row=col.row()
row.prop(settings, "linear", text='Linear')
row.prop(settings, "expand_on_selected_bones")
@ -87,8 +98,11 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
col.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
## show a dropdown allowing to go back to unpinned, unbaked version of the animation
# if ob.type == 'ARMATURE':
# pass
if ob and ob.type == 'ARMATURE':
if ob.animation_data and ob.animation_data.action:
if 'baked' in ob.animation_data.action.name or 'pinned' in ob.animation_data.action.name:
col.operator('uac.step_back_actions', text='Use Previous Actions', icon= 'ACTION')