action swap and improved object / curve switch
0.8.0 - Easy jump to previous action - improved armature <-> curve back and forth - better UImaster
parent
427a3ad4b2
commit
b983183504
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
40
panels.py
40
panels.py
|
@ -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')
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue