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
|
# Changelog
|
||||||
|
|
||||||
|
0.8.0
|
||||||
|
|
||||||
|
- Easy jump to previous action
|
||||||
|
- improved armature <-> curve back and forth
|
||||||
|
- better UI
|
||||||
|
|
||||||
0.7.1
|
0.7.1
|
||||||
|
|
||||||
- customizable panel category name
|
- customizable panel category name
|
||||||
|
|
|
@ -389,8 +389,6 @@ def pin_down_feets():
|
||||||
bpy.data.collections.remove(tmp_col)
|
bpy.data.collections.remove(tmp_col)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UAC_OT_pin_feets(bpy.types.Operator):
|
class UAC_OT_pin_feets(bpy.types.Operator):
|
||||||
bl_idname = "anim.pin_feets"
|
bl_idname = "anim.pin_feets"
|
||||||
bl_label = "Pin Feets"
|
bl_label = "Pin Feets"
|
||||||
|
@ -411,9 +409,73 @@ class UAC_OT_pin_feets(bpy.types.Operator):
|
||||||
|
|
||||||
return {"FINISHED"}
|
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=(
|
classes=(
|
||||||
UAC_OT_bake_cycle_and_step,
|
UAC_OT_bake_cycle_and_step,
|
||||||
UAC_OT_pin_feets,
|
UAC_OT_pin_feets,
|
||||||
|
UAC_OT_set_action,
|
||||||
|
UAC_OT_step_back_actions,
|
||||||
)
|
)
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
|
|
|
@ -226,18 +226,33 @@ class UAC_OT_edit_curve(bpy.types.Operator):
|
||||||
context.view_layer.objects.active = curve
|
context.view_layer.objects.active = curve
|
||||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # EDIT_CURVE
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False) # EDIT_CURVE
|
||||||
|
|
||||||
|
b.id_data.select_set(False)
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
class UAC_OT_set_choice_id(bpy.types.Operator):
|
class UAC_OT_go_to_object(bpy.types.Operator):
|
||||||
bl_idname = "uac.set_choice_id"
|
bl_idname = "uac.go_to_object"
|
||||||
bl_label = "Chosen ID"
|
bl_label = "Go To Object"
|
||||||
bl_description = "Set passed id to a custom prop in window manager"
|
bl_description = "Go to object in pose mode"
|
||||||
bl_options = {"REGISTER", "INTERNAL"}
|
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):
|
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"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
class UAC_OT_object_from_curve(bpy.types.Operator):
|
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')
|
self.report({'ERROR'}, 'No armature using this curve found')
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
curve.select_set(False)
|
||||||
if len(self.armatures) > 1:
|
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
|
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)
|
return self.execute(context)
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
for i, item in enumerate(self.armatures):
|
for arm, pb in self.armatures:
|
||||||
arm, pb = item
|
layout.operator('uac.go_to_object', text=f'{arm.name} ({pb.name})', icon='OUTLINER_OB_ARMATURE').obj_name = arm.name
|
||||||
layout.operator('uac.set_choice_id', text=f'{arm.name} > {pb.name}', icon='OUTLINER_OB_ARMATURE').idx = i
|
|
||||||
|
|
||||||
def execute(self, context):
|
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"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
classes=(
|
classes=(
|
||||||
|
@ -298,7 +311,7 @@ UAC_OT_create_curve_path,
|
||||||
UAC_OT_create_follow_path,
|
UAC_OT_create_follow_path,
|
||||||
UAC_OT_snap_curve_to_ground,
|
UAC_OT_snap_curve_to_ground,
|
||||||
UAC_OT_edit_curve,
|
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
|
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",
|
"name": "Unfold Anim Cycle",
|
||||||
"description": "Anim tools to develop walk/run cycles along a curve",
|
"description": "Anim tools to develop walk/run cycles along a curve",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (0, 7, 1),
|
"version": (0, 8, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "WIP",
|
"warning": "WIP",
|
||||||
|
|
38
panels.py
38
panels.py
|
@ -24,8 +24,25 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
||||||
row.prop(settings, "forward_axis", text='')
|
row.prop(settings, "forward_axis", text='')
|
||||||
layout.operator("uac.autoset_axis", text='Auto-Set Axis')
|
layout.operator("uac.autoset_axis", text='Auto-Set Axis')
|
||||||
|
|
||||||
if not settings.path_to_follow:
|
pb = None
|
||||||
layout.operator('anim.create_curve_path', text='Create Curve at Root Position', icon='CURVE_BEZCURVE')
|
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
|
||||||
|
|
||||||
|
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()
|
box = layout.box()
|
||||||
expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT'
|
expand_icon = 'TRIA_DOWN' if tweak else 'TRIA_RIGHT'
|
||||||
|
@ -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...)
|
# Determine if already has a constraint (a bit too much condition in a panel...)
|
||||||
constrained = False
|
|
||||||
if ob:
|
if ob:
|
||||||
if ob.type == 'ARMATURE':
|
if ob.type == 'ARMATURE':
|
||||||
pb = ob.pose.bones.get(prefs.tgt_bone)
|
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 context.mode == 'POSE':
|
||||||
if not constrained:
|
if not constrained:
|
||||||
box.operator('anim.create_follow_path', text='Add follow path constraint', icon='CON_FOLLOWPATH')
|
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()
|
box = layout.box()
|
||||||
col=box.column()
|
col=box.column()
|
||||||
|
@ -75,7 +86,7 @@ class UAC_PT_walk_cycle_anim_panel(bpy.types.Panel):
|
||||||
## Bake cycle (on selected)
|
## Bake cycle (on selected)
|
||||||
box = layout.box()
|
box = layout.box()
|
||||||
col=box.column()
|
col=box.column()
|
||||||
col.label(text='Keys:')
|
col.label(text='Actions:')
|
||||||
row=col.row()
|
row=col.row()
|
||||||
row.prop(settings, "linear", text='Linear')
|
row.prop(settings, "linear", text='Linear')
|
||||||
row.prop(settings, "expand_on_selected_bones")
|
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')
|
col.operator('anim.pin_feets', text='Pin feets', icon='PINNED')
|
||||||
|
|
||||||
## show a dropdown allowing to go back to unpinned, unbaked version of the animation
|
## show a dropdown allowing to go back to unpinned, unbaked version of the animation
|
||||||
# if ob.type == 'ARMATURE':
|
if ob and ob.type == 'ARMATURE':
|
||||||
# pass
|
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