diff options
author | Sybren A. Stüvel <sybren@stuvel.eu> | 2021-07-05 18:35:44 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@stuvel.eu> | 2021-07-05 18:35:44 +0300 |
commit | 55b0e323ba2e8b09a27db0817b30a3978e2b45f6 (patch) | |
tree | 49a274f0b296c453f441dd65ff139cfc7cf6e138 | |
parent | a7838b9b29bb6cd02f306931cae2588b4e548fa8 (diff) |
Pose Library: allow converting old pose libraries to new pose assets
The Dopesheet side-panel now has a button "Convert old-style pose library"
that converts each frame with a pose marker to a pose asset.
The preview images are rendered on the current frame.
-rw-r--r-- | pose_library/__init__.py | 3 | ||||
-rw-r--r-- | pose_library/conversion.py | 78 | ||||
-rw-r--r-- | pose_library/gui.py | 2 | ||||
-rw-r--r-- | pose_library/operators.py | 32 | ||||
-rw-r--r-- | pose_library/pose_creation.py | 74 |
5 files changed, 163 insertions, 26 deletions
diff --git a/pose_library/__init__.py b/pose_library/__init__.py index a9f880f5..c72d9426 100644 --- a/pose_library/__init__.py +++ b/pose_library/__init__.py @@ -36,7 +36,7 @@ bl_info = { from typing import List, Tuple _need_reload = "operators" in locals() -from . import gui, keymaps, macros, operators +from . import gui, keymaps, macros, operators, conversion if _need_reload: import importlib @@ -45,6 +45,7 @@ if _need_reload: keymaps = importlib.reload(keymaps) macros = importlib.reload(macros) operators = importlib.reload(operators) + conversion = importlib.reload(conversion) import bpy diff --git a/pose_library/conversion.py b/pose_library/conversion.py new file mode 100644 index 00000000..43a5d3a4 --- /dev/null +++ b/pose_library/conversion.py @@ -0,0 +1,78 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +""" +Pose Library - Conversion of old pose libraries. +""" + +from typing import Optional +from collections.abc import Collection + +if "pose_creation" not in locals(): + from . import pose_creation +else: + import importlib + + pose_creation = importlib.reload(pose_creation) + +import bpy +from bpy.types import ( + Action, + TimelineMarker, +) + + +def convert_old_poselib(old_poselib: Action) -> Collection[Action]: + """Convert an old-style pose library to a set of pose Actions. + + Old pose libraries were one Action with multiple pose markers. Each pose + marker will be converted to an Action by itself and marked as asset. + """ + + pose_assets = [ + action + for marker in old_poselib.pose_markers + if (action := convert_old_pose(old_poselib, marker)) + ] + + # Mark all Actions as assets in one go. Ideally this would be done on an + # appropriate frame in the scene (to set up things like the background + # colour), but the old-style poselib doesn't contain such information. All + # we can do is just render on the current frame. + bpy.ops.asset.mark({'selected_ids': pose_assets}) + + return pose_assets + + +def convert_old_pose(old_poselib: Action, marker: TimelineMarker) -> Optional[Action]: + """Convert an old-style pose library pose to a pose action.""" + + frame: int = marker.frame + action: Optional[Action] = None + + for fcurve in old_poselib.fcurves: + key = pose_creation.find_keyframe(fcurve, frame) + if not key: + continue + + if action is None: + action = bpy.data.actions.new(marker.name) + + pose_creation.create_single_key_fcurve(action, fcurve, key) + + return action diff --git a/pose_library/gui.py b/pose_library/gui.py index bd963f1a..bf2693e9 100644 --- a/pose_library/gui.py +++ b/pose_library/gui.py @@ -174,6 +174,8 @@ class DOPESHEET_PT_asset_panel(Panel): row.operator("poselib.restore_previous_action", text="", icon='LOOP_BACK') col.operator("poselib.copy_as_asset", icon="COPYDOWN") + layout.operator("poselib.convert_old_poselib") + classes = ( ASSETBROWSER_PT_pose_library_editing, diff --git a/pose_library/operators.py b/pose_library/operators.py index fee94bad..0474f6e7 100644 --- a/pose_library/operators.py +++ b/pose_library/operators.py @@ -393,10 +393,42 @@ class POSELIB_OT_apply_pose_asset_for_keymap(Operator): return bpy.ops.poselib.apply_pose_asset(context.copy(), 'EXEC_DEFAULT', flipped=flipped) +class POSELIB_OT_convert_old_poselib(Operator): + bl_idname = "poselib.convert_old_poselib" + bl_label = "Convert old-style pose library" + bl_description = "Create a pose asset for each pose marker in the current action" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context: Context) -> bool: + action = context.object and context.object.animation_data and context.object.animation_data.action + if not action: + cls.poll_message_set("Active object has no Action") + return False + if not action.pose_markers: + cls.poll_message_set("Action %r is not a old-style pose library" % action.name) + return False + return True + + def execute(self, context: Context) -> Set[str]: + from . import conversion + + old_poselib = context.object.animation_data.action + new_actions = conversion.convert_old_poselib(old_poselib) + + if not new_actions: + self.report({'ERROR'}, "Unable to convert to pose assets") + return {'CANCELLED'} + + self.report({'INFO'}, "Converted %d poses to pose assets" % len(new_actions)) + return {'FINISHED'} + + classes = ( ASSET_OT_assign_action, POSELIB_OT_apply_pose_asset_for_keymap, POSELIB_OT_blend_pose_asset_for_keymap, + POSELIB_OT_convert_old_poselib, POSELIB_OT_copy_as_asset, POSELIB_OT_create_pose_asset, POSELIB_OT_paste_asset, diff --git a/pose_library/pose_creation.py b/pose_library/pose_creation.py index da72ed64..79efcae4 100644 --- a/pose_library/pose_creation.py +++ b/pose_library/pose_creation.py @@ -355,35 +355,59 @@ def copy_fcurves( keyframe = find_keyframe(fcurve, src_frame_nr) if keyframe is None: continue - # Create an FCurve and copy some properties. - src_group_name = fcurve.group.name if fcurve.group else "" - dst_fcurve = dst_action.fcurves.new( - fcurve.data_path, index=fcurve.array_index, action_group=src_group_name - ) - for propname in {"auto_smoothing", "color", "color_mode", "extrapolation"}: - setattr(dst_fcurve, propname, getattr(fcurve, propname)) - - dst_keyframe = dst_fcurve.keyframe_points.insert( - keyframe.co.x, keyframe.co.y, keyframe_type=keyframe.type - ) - - for propname in { - "amplitude", - "back", - "easing", - "handle_left", - "handle_left_type", - "handle_right", - "handle_right_type", - "interpolation", - "period", - }: - setattr(dst_keyframe, propname, getattr(keyframe, propname)) - dst_fcurve.update() + create_single_key_fcurve(dst_action, fcurve, keyframe) num_fcurves_copied += 1 return num_fcurves_copied +def create_single_key_fcurve( + dst_action: Action, src_fcurve: FCurve, src_keyframe: Keyframe +) -> FCurve: + """Create a copy of the source FCurve, but only for the given keyframe. + + Returns a new FCurve with just one keyframe. + """ + + dst_fcurve = copy_fcurve_without_keys(dst_action, src_fcurve) + copy_keyframe(dst_fcurve, src_keyframe) + return dst_fcurve + + +def copy_fcurve_without_keys(dst_action: Action, src_fcurve: FCurve) -> FCurve: + """Create a new FCurve and copy some properties.""" + + src_group_name = src_fcurve.group.name if src_fcurve.group else "" + dst_fcurve = dst_action.fcurves.new( + src_fcurve.data_path, index=src_fcurve.array_index, action_group=src_group_name + ) + for propname in {"auto_smoothing", "color", "color_mode", "extrapolation"}: + setattr(dst_fcurve, propname, getattr(src_fcurve, propname)) + return dst_fcurve + + +def copy_keyframe(dst_fcurve: FCurve, src_keyframe: Keyframe) -> Keyframe: + """Copy a keyframe from one FCurve to the other.""" + + dst_keyframe = dst_fcurve.keyframe_points.insert( + src_keyframe.co.x, src_keyframe.co.y, options={'FAST'}, keyframe_type=src_keyframe.type + ) + + for propname in { + "amplitude", + "back", + "easing", + "handle_left", + "handle_left_type", + "handle_right", + "handle_right_type", + "interpolation", + "period", + }: + setattr(dst_keyframe, propname, getattr(src_keyframe, propname)) + dst_fcurve.update() + return dst_keyframe + + def find_keyframe(fcurve: FCurve, frame: float) -> Optional[Keyframe]: # Binary search adapted from https://pythonguides.com/python-binary-search/ keyframes = fcurve.keyframe_points |