Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender')
-rw-r--r--source/blender/editors/armature/CMakeLists.txt3
-rw-r--r--source/blender/editors/armature/armature_intern.h4
-rw-r--r--source/blender/editors/armature/armature_ops.c2
-rw-r--r--source/blender/editors/armature/pose_backup.c139
-rw-r--r--source/blender/editors/armature/pose_lib_2.c638
-rw-r--r--source/blender/editors/include/ED_armature.h16
6 files changed, 802 insertions, 0 deletions
diff --git a/source/blender/editors/armature/CMakeLists.txt b/source/blender/editors/armature/CMakeLists.txt
index 0030e78002b..e942bcf2902 100644
--- a/source/blender/editors/armature/CMakeLists.txt
+++ b/source/blender/editors/armature/CMakeLists.txt
@@ -20,6 +20,7 @@ set(INC
../../blenfont
../../blenkernel
../../blenlib
+ ../../blenloader
../../blentranslation
../../depsgraph
../../gpu
@@ -43,9 +44,11 @@ set(SRC
armature_utils.c
editarmature_undo.c
meshlaplacian.c
+ pose_backup.c
pose_edit.c
pose_group.c
pose_lib.c
+ pose_lib_2.c
pose_select.c
pose_slide.c
pose_transform.c
diff --git a/source/blender/editors/armature/armature_intern.h b/source/blender/editors/armature/armature_intern.h
index d429e51061b..f9950d27e97 100644
--- a/source/blender/editors/armature/armature_intern.h
+++ b/source/blender/editors/armature/armature_intern.h
@@ -203,6 +203,10 @@ void POSELIB_OT_pose_move(struct wmOperatorType *ot);
void POSELIB_OT_browse_interactive(struct wmOperatorType *ot);
void POSELIB_OT_apply_pose(struct wmOperatorType *ot);
+/* pose_lib_2.c */
+void POSELIB_OT_apply_pose_asset(struct wmOperatorType *ot);
+void POSELIB_OT_blend_pose_asset(struct wmOperatorType *ot);
+
/* ******************************************************* */
/* Pose Sliding Tools */
/* pose_slide.c */
diff --git a/source/blender/editors/armature/armature_ops.c b/source/blender/editors/armature/armature_ops.c
index a0face26bae..fbd89106de5 100644
--- a/source/blender/editors/armature/armature_ops.c
+++ b/source/blender/editors/armature/armature_ops.c
@@ -131,6 +131,8 @@ void ED_operatortypes_armature(void)
/* POSELIB */
WM_operatortype_append(POSELIB_OT_browse_interactive);
WM_operatortype_append(POSELIB_OT_apply_pose);
+ WM_operatortype_append(POSELIB_OT_apply_pose_asset);
+ WM_operatortype_append(POSELIB_OT_blend_pose_asset);
WM_operatortype_append(POSELIB_OT_pose_add);
WM_operatortype_append(POSELIB_OT_pose_remove);
diff --git a/source/blender/editors/armature/pose_backup.c b/source/blender/editors/armature/pose_backup.c
new file mode 100644
index 00000000000..dffcd9bdc5a
--- /dev/null
+++ b/source/blender/editors/armature/pose_backup.c
@@ -0,0 +1,139 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup edarmature
+ */
+
+#include "ED_armature.h"
+
+#include <string.h>
+
+#include "BLI_listbase.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "DNA_action_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_action.h"
+#include "BKE_armature.h"
+#include "BKE_idprop.h"
+
+/* simple struct for storing backup info for one pose channel */
+typedef struct PoseChannelBackup {
+ struct PoseChannelBackup *next, *prev;
+
+ struct bPoseChannel *pchan; /* Pose channel this backup is for. */
+
+ struct bPoseChannel olddata; /* Backup of pose channel. */
+ struct IDProperty *oldprops; /* Backup copy (needs freeing) of pose channel's ID properties. */
+} PoseChannelBackup;
+
+typedef struct PoseBackup {
+ bool is_bone_selection_relevant;
+ ListBase /* PoseChannelBackup* */ backups;
+} PoseBackup;
+
+static PoseBackup *pose_backup_create(const Object *ob,
+ const bAction *action,
+ const bool is_bone_selection_relevant)
+{
+ ListBase backups = {NULL, NULL};
+ const bArmature *armature = ob->data;
+
+ /* TODO(Sybren): reuse same approach as in `armature_pose.cc` in this function, as that doesn't
+ * have the assumption that action group names are bone names. */
+ LISTBASE_FOREACH (bActionGroup *, agrp, &action->groups) {
+ bPoseChannel *pchan = BKE_pose_channel_find_name(ob->pose, agrp->name);
+ if (pchan == NULL) {
+ continue;
+ }
+
+ if (is_bone_selection_relevant && !PBONE_SELECTED(armature, pchan->bone)) {
+ continue;
+ }
+
+ PoseChannelBackup *chan_bak = MEM_callocN(sizeof(*chan_bak), "PoseChannelBackup");
+ chan_bak->pchan = pchan;
+ memcpy(&chan_bak->olddata, chan_bak->pchan, sizeof(chan_bak->olddata));
+
+ if (pchan->prop) {
+ chan_bak->oldprops = IDP_CopyProperty(pchan->prop);
+ }
+
+ BLI_addtail(&backups, chan_bak);
+ }
+
+ /* PoseBackup is constructed late, so that the above loop can use stack variables. */
+ PoseBackup *pose_backup = MEM_callocN(sizeof(*pose_backup), __func__);
+ pose_backup->is_bone_selection_relevant = is_bone_selection_relevant;
+ pose_backup->backups = backups;
+ return pose_backup;
+}
+
+PoseBackup *ED_pose_backup_create_all_bones(const Object *ob, const bAction *action)
+{
+ return pose_backup_create(ob, action, false);
+}
+
+PoseBackup *ED_pose_backup_create_selected_bones(const Object *ob, const bAction *action)
+{
+ /* See if bone selection is relevant. */
+ bool all_bones_selected = true;
+ bool no_bones_selected = true;
+ const bArmature *armature = ob->data;
+ LISTBASE_FOREACH (bPoseChannel *, pchan, &ob->pose->chanbase) {
+ const bool is_selected = PBONE_SELECTED(armature, pchan->bone);
+ all_bones_selected &= is_selected;
+ no_bones_selected &= !is_selected;
+ }
+
+ /* If no bones are selected, act as if all are. */
+ const bool is_bone_selection_relevant = !all_bones_selected && !no_bones_selected;
+ return pose_backup_create(ob, action, is_bone_selection_relevant);
+}
+
+bool ED_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup)
+{
+ return pose_backup->is_bone_selection_relevant;
+}
+
+void ED_pose_backup_restore(const PoseBackup *pbd)
+{
+ LISTBASE_FOREACH (PoseChannelBackup *, chan_bak, &pbd->backups) {
+ memcpy(chan_bak->pchan, &chan_bak->olddata, sizeof(chan_bak->olddata));
+
+ if (chan_bak->oldprops) {
+ IDP_SyncGroupValues(chan_bak->pchan->prop, chan_bak->oldprops);
+ }
+
+ /* TODO: constraints settings aren't restored yet,
+ * even though these could change (though not that likely) */
+ }
+}
+
+void ED_pose_backup_free(PoseBackup *pbd)
+{
+ LISTBASE_FOREACH_MUTABLE (PoseChannelBackup *, chan_bak, &pbd->backups) {
+ if (chan_bak->oldprops) {
+ IDP_FreeProperty(chan_bak->oldprops);
+ }
+ BLI_freelinkN(&pbd->backups, chan_bak);
+ }
+ MEM_freeN(pbd);
+}
diff --git a/source/blender/editors/armature/pose_lib_2.c b/source/blender/editors/armature/pose_lib_2.c
new file mode 100644
index 00000000000..eb091296282
--- /dev/null
+++ b/source/blender/editors/armature/pose_lib_2.c
@@ -0,0 +1,638 @@
+/*
+ * 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.
+ *
+ * The Original Code is Copyright (C) 2021, Blender Foundation
+ */
+
+/** \file
+ * \ingroup edarmature
+ */
+
+#include <math.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_string.h"
+
+#include "BLT_translation.h"
+
+#include "DNA_armature_types.h"
+
+#include "BKE_action.h"
+#include "BKE_anim_data.h"
+#include "BKE_animsys.h"
+#include "BKE_armature.h"
+#include "BKE_context.h"
+#include "BKE_lib_id.h"
+#include "BKE_object.h"
+#include "BKE_report.h"
+
+#include "DEG_depsgraph.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "UI_interface.h"
+
+#include "ED_armature.h"
+#include "ED_asset.h"
+#include "ED_keyframing.h"
+#include "ED_screen.h"
+
+#include "armature_intern.h"
+
+typedef enum ePoseBlendState {
+ POSE_BLEND_INIT,
+ POSE_BLEND_BLENDING,
+ POSE_BLEND_ORIGINAL,
+ POSE_BLEND_CONFIRM,
+ POSE_BLEND_CANCEL,
+} ePoseBlendState;
+
+typedef struct PoseBlendData {
+ ePoseBlendState state;
+ bool needs_redraw;
+
+ struct {
+ bool use_release_confirm;
+ int drag_start_xy[2];
+ int init_event_type;
+
+ bool cursor_wrap_enabled;
+ } release_confirm_info;
+
+ /* For temp-loading the Action from the pose library. */
+ AssetTempIDConsumer *temp_id_consumer;
+
+ /* Blend factor, interval [0, 1] for interpolating between current and given pose. */
+ float blend_factor;
+ struct PoseBackup *pose_backup;
+
+ Object *ob; /* Object to work on. */
+ bAction *act; /* Pose to blend into the current pose. */
+ bool free_action;
+
+ Scene *scene; /* For auto-keying. */
+ ScrArea *area; /* For drawing status text. */
+
+ /** Info-text to print in header. */
+ char headerstr[UI_MAX_DRAW_STR];
+} PoseBlendData;
+
+/* Makes a copy of the current pose for restoration purposes - doesn't do constraints currently */
+static void poselib_backup_posecopy(PoseBlendData *pbd)
+{
+ pbd->pose_backup = ED_pose_backup_create_selected_bones(pbd->ob, pbd->act);
+
+ if (pbd->state == POSE_BLEND_INIT) {
+ /* Ready for blending now. */
+ pbd->state = POSE_BLEND_BLENDING;
+ }
+}
+
+/* ---------------------------- */
+
+/* Auto-key/tag bones affected by the pose Action. */
+static void poselib_keytag_pose(bContext *C, Scene *scene, PoseBlendData *pbd)
+{
+ if (!autokeyframe_cfra_can_key(scene, &pbd->ob->id)) {
+ return;
+ }
+
+ AnimData *adt = BKE_animdata_from_id(&pbd->ob->id);
+ if (adt != NULL && adt->action != NULL && ID_IS_LINKED(&adt->action->id)) {
+ /* Changes to linked-in Actions are not allowed. */
+ return;
+ }
+
+ bPose *pose = pbd->ob->pose;
+ bAction *act = pbd->act;
+
+ KeyingSet *ks = ANIM_get_keyingset_for_autokeying(scene, ANIM_KS_WHOLE_CHARACTER_ID);
+ ListBase dsources = {NULL, NULL};
+
+ /* start tagging/keying */
+ const bArmature *armature = pbd->ob->data;
+ LISTBASE_FOREACH (bActionGroup *, agrp, &act->groups) {
+ /* only for selected bones unless there aren't any selected, in which case all are included */
+ bPoseChannel *pchan = BKE_pose_channel_find_name(pose, agrp->name);
+ if (pchan == NULL) {
+ continue;
+ }
+
+ if (ED_pose_backup_is_selection_relevant(pbd->pose_backup) &&
+ !PBONE_SELECTED(armature, pchan->bone)) {
+ continue;
+ }
+
+ /* Add data-source override for the PoseChannel, to be used later. */
+ ANIM_relative_keyingset_add_source(&dsources, &pbd->ob->id, &RNA_PoseBone, pchan);
+ }
+
+ /* Perform actual auto-keying. */
+ ANIM_apply_keyingset(C, &dsources, NULL, ks, MODIFYKEY_MODE_INSERT, (float)CFRA);
+ BLI_freelistN(&dsources);
+
+ /* send notifiers for this */
+ WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, NULL);
+}
+
+/* Apply the relevant changes to the pose */
+static void poselib_blend_apply(bContext *C, wmOperator *op)
+{
+ PoseBlendData *pbd = (PoseBlendData *)op->customdata;
+
+ if (pbd->state == POSE_BLEND_BLENDING) {
+ BLI_snprintf(pbd->headerstr,
+ sizeof(pbd->headerstr),
+ TIP_("PoseLib blending: \"%s\" at %3.0f%%"),
+ pbd->act->id.name + 2,
+ pbd->blend_factor * 100);
+ ED_area_status_text(pbd->area, pbd->headerstr);
+
+ ED_workspace_status_text(
+ C, TIP_("Tab: show original pose; Horizontal mouse movement: change blend percentage"));
+ }
+ else {
+ ED_area_status_text(pbd->area, TIP_("PoseLib showing original pose"));
+ ED_workspace_status_text(C, TIP_("Tab: show blended pose"));
+ }
+
+ if (!pbd->needs_redraw) {
+ return;
+ }
+ pbd->needs_redraw = false;
+
+ ED_pose_backup_restore(pbd->pose_backup);
+
+ /* The pose needs updating, whether it's for restoring the original pose or for showing the
+ * result of the blend. */
+ DEG_id_tag_update(&pbd->ob->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, pbd->ob);
+
+ if (pbd->state != POSE_BLEND_BLENDING) {
+ return;
+ }
+
+ /* Perform the actual blending. */
+ struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
+ AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, 0.0f);
+ BKE_pose_apply_action_blend(pbd->ob, pbd->act, &anim_eval_context, pbd->blend_factor);
+}
+
+/* ---------------------------- */
+
+static void poselib_blend_set_factor(PoseBlendData *pbd, const float new_factor)
+{
+ pbd->blend_factor = CLAMPIS(new_factor, 0.0f, 1.0f);
+ pbd->needs_redraw = true;
+}
+
+static void poselib_slide_mouse_update_blendfactor(PoseBlendData *pbd, const wmEvent *event)
+{
+ if (pbd->release_confirm_info.use_release_confirm) {
+ /* Release confirm calculates factor based on where the dragging was started from. */
+ const float range = 300 * U.pixelsize;
+ const float new_factor = (event->x - pbd->release_confirm_info.drag_start_xy[0]) / range;
+ poselib_blend_set_factor(pbd, new_factor);
+ }
+ else {
+ const float new_factor = (event->x - pbd->area->v1->vec.x) / ((float)pbd->area->winx);
+ poselib_blend_set_factor(pbd, new_factor);
+ }
+}
+
+/* Return operator return value. */
+static int poselib_blend_handle_event(bContext *UNUSED(C), wmOperator *op, const wmEvent *event)
+{
+ PoseBlendData *pbd = op->customdata;
+
+ if (event->type == MOUSEMOVE) {
+ poselib_slide_mouse_update_blendfactor(pbd, event);
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ /* Handle the release confirm event directly, it has priority over others. */
+ if (pbd->release_confirm_info.use_release_confirm &&
+ (event->type == pbd->release_confirm_info.init_event_type) && (event->val == KM_RELEASE)) {
+ pbd->state = POSE_BLEND_CONFIRM;
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ /* only accept 'press' event, and ignore 'release', so that we don't get double actions */
+ if (ELEM(event->val, KM_PRESS, KM_NOTHING) == 0) {
+ return OPERATOR_RUNNING_MODAL;
+ }
+
+ /* NORMAL EVENT HANDLING... */
+ /* searching takes priority over normal activity */
+ switch (event->type) {
+ /* Exit - cancel. */
+ case EVT_ESCKEY:
+ case RIGHTMOUSE:
+ pbd->state = POSE_BLEND_CANCEL;
+ break;
+
+ /* Exit - confirm. */
+ case LEFTMOUSE:
+ case EVT_RETKEY:
+ case EVT_PADENTER:
+ case EVT_SPACEKEY:
+ pbd->state = POSE_BLEND_CONFIRM;
+ break;
+
+ /* TODO(Sybren): toggle between original pose and poselib pose. */
+ case EVT_TABKEY:
+ pbd->state = pbd->state == POSE_BLEND_BLENDING ? POSE_BLEND_ORIGINAL : POSE_BLEND_BLENDING;
+ pbd->needs_redraw = true;
+ break;
+
+ /* TODO(Sybren): use better UI for slider. */
+ }
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static void poselib_blend_cursor_update(bContext *C, wmOperator *op)
+{
+ PoseBlendData *pbd = op->customdata;
+
+ /* Ensure cursor-grab (continuous grabbing) is enabled when using release-confirm. */
+ if (pbd->release_confirm_info.use_release_confirm &&
+ !pbd->release_confirm_info.cursor_wrap_enabled) {
+ WM_cursor_grab_enable(CTX_wm_window(C), WM_CURSOR_WRAP_XY, true, NULL);
+ pbd->release_confirm_info.cursor_wrap_enabled = true;
+ }
+}
+
+/* ---------------------------- */
+
+static Object *get_poselib_object(bContext *C)
+{
+ if (C == NULL) {
+ return NULL;
+ }
+ return BKE_object_pose_armature_get(CTX_data_active_object(C));
+}
+
+static void poselib_tempload_exit(PoseBlendData *pbd)
+{
+ ED_asset_temp_id_consumer_free(&pbd->temp_id_consumer);
+}
+
+static bAction *poselib_blend_init_get_action(bContext *C, wmOperator *op)
+{
+ bool asset_handle_valid;
+ const AssetLibraryReference *asset_library = CTX_wm_asset_library(C);
+ const AssetHandle asset_handle = CTX_wm_asset_handle(C, &asset_handle_valid);
+ /* Poll callback should check. */
+ BLI_assert((asset_library != NULL) && asset_handle_valid);
+
+ PoseBlendData *pbd = op->customdata;
+
+ pbd->temp_id_consumer = ED_asset_temp_id_consumer_create(&asset_handle);
+ return (bAction *)ED_asset_temp_id_consumer_ensure_local_id(
+ pbd->temp_id_consumer, C, asset_library, ID_AC, CTX_data_main(C), op->reports);
+}
+
+static bAction *flip_pose(bContext *C, Object *ob, bAction *action)
+{
+ bAction *action_copy = (bAction *)BKE_id_copy_ex(NULL, &action->id, NULL, LIB_ID_COPY_LOCALIZE);
+
+ /* Lock the window manager while flipping the pose. Flipping requires temporarily modifying the
+ * pose, which can cause unwanted visual glitches. */
+ wmWindowManager *wm = CTX_wm_manager(C);
+ const bool interface_was_locked = CTX_wm_interface_locked(C);
+ WM_set_locked_interface(wm, true);
+
+ BKE_action_flip_with_pose(action_copy, ob);
+
+ WM_set_locked_interface(wm, interface_was_locked);
+ return action_copy;
+}
+
+/* Return true on success, false if the context isn't suitable. */
+static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ op->customdata = NULL;
+
+ /* check if valid poselib */
+ Object *ob = get_poselib_object(C);
+ if (ELEM(NULL, ob, ob->pose, ob->data)) {
+ BKE_report(op->reports, RPT_ERROR, TIP_("Pose lib is only for armatures in pose mode"));
+ return false;
+ }
+
+ /* Set up blend state info. */
+ PoseBlendData *pbd;
+ op->customdata = pbd = MEM_callocN(sizeof(PoseBlendData), "PoseLib Preview Data");
+
+ bAction *action = poselib_blend_init_get_action(C, op);
+ if (action == NULL) {
+ return false;
+ }
+
+ /* Maybe flip the Action. */
+ const bool apply_flipped = RNA_boolean_get(op->ptr, "flipped");
+ if (apply_flipped) {
+ action = flip_pose(C, ob, action);
+ pbd->free_action = true;
+ }
+ pbd->act = action;
+
+ /* Get the basic data. */
+ pbd->ob = ob;
+ pbd->ob->pose = ob->pose;
+
+ pbd->scene = CTX_data_scene(C);
+ pbd->area = CTX_wm_area(C);
+
+ pbd->state = POSE_BLEND_INIT;
+ pbd->needs_redraw = true;
+ pbd->blend_factor = RNA_float_get(op->ptr, "blend_factor");
+ /* Just to avoid a clang-analyzer warning (false positive), it's set properly below. */
+ pbd->release_confirm_info.use_release_confirm = false;
+
+ /* Release confirm data. Only available if there's an event to work with. */
+ if (event != NULL) {
+ PropertyRNA *release_confirm_prop = RNA_struct_find_property(op->ptr, "release_confirm");
+ pbd->release_confirm_info.use_release_confirm = (release_confirm_prop != NULL) &&
+ RNA_property_boolean_get(op->ptr,
+ release_confirm_prop);
+ }
+
+ if (pbd->release_confirm_info.use_release_confirm) {
+ BLI_assert(event != NULL);
+ pbd->release_confirm_info.drag_start_xy[0] = event->x;
+ pbd->release_confirm_info.drag_start_xy[1] = event->y;
+ pbd->release_confirm_info.init_event_type = WM_userdef_event_type_from_keymap_type(
+ event->type);
+ }
+
+ /* Make backups for blending and restoring the pose. */
+ poselib_backup_posecopy(pbd);
+
+ /* Set pose flags to ensure the depsgraph evaluation doesn't overwrite it. */
+ pbd->ob->pose->flag &= ~POSE_DO_UNLOCK;
+ pbd->ob->pose->flag |= POSE_LOCKED;
+
+ return true;
+}
+
+static void poselib_blend_cleanup(bContext *C, wmOperator *op)
+{
+ PoseBlendData *pbd = op->customdata;
+ wmWindow *win = CTX_wm_window(C);
+
+ /* Redraw the header so that it doesn't show any of our stuff anymore. */
+ ED_area_status_text(pbd->area, NULL);
+ ED_workspace_status_text(C, NULL);
+
+ /* This signals the depsgraph to unlock and reevaluate the pose on the next evaluation. */
+ bPose *pose = pbd->ob->pose;
+ pose->flag |= POSE_DO_UNLOCK;
+
+ switch (pbd->state) {
+ case POSE_BLEND_CONFIRM: {
+ Scene *scene = pbd->scene;
+ poselib_keytag_pose(C, scene, pbd);
+
+ /* Ensure the redo panel has the actually-used value, instead of the initial value. */
+ RNA_float_set(op->ptr, "blend_factor", pbd->blend_factor);
+ break;
+ }
+
+ case POSE_BLEND_INIT:
+ case POSE_BLEND_BLENDING:
+ case POSE_BLEND_ORIGINAL:
+ /* Cleanup should not be called directly from these states. */
+ BLI_assert(!"poselib_blend_cleanup: unexpected pose blend state");
+ BKE_report(op->reports, RPT_ERROR, "Internal pose library error, cancelling operator");
+ ATTR_FALLTHROUGH;
+ case POSE_BLEND_CANCEL:
+ ED_pose_backup_restore(pbd->pose_backup);
+ break;
+ }
+
+ if (pbd->release_confirm_info.cursor_wrap_enabled) {
+ WM_cursor_grab_disable(win, pbd->release_confirm_info.drag_start_xy);
+ pbd->release_confirm_info.cursor_wrap_enabled = false;
+ }
+
+ DEG_id_tag_update(&pbd->ob->id, ID_RECALC_GEOMETRY);
+ WM_event_add_notifier(C, NC_OBJECT | ND_POSE, pbd->ob);
+ /* Update mouse-hover highlights. */
+ WM_event_add_mousemove(win);
+}
+
+static void poselib_blend_free(wmOperator *op)
+{
+ PoseBlendData *pbd = op->customdata;
+ if (pbd == NULL) {
+ return;
+ }
+
+ if (pbd->free_action) {
+ /* Run before #poselib_tempload_exit to avoid any problems from indirectly
+ * referenced ID pointers. */
+ BKE_id_free(NULL, pbd->act);
+ }
+ poselib_tempload_exit(pbd);
+
+ /* Must have been dealt with before! */
+ BLI_assert(pbd->release_confirm_info.cursor_wrap_enabled == false);
+
+ /* Free temp data for operator */
+ ED_pose_backup_free(pbd->pose_backup);
+ pbd->pose_backup = NULL;
+
+ MEM_SAFE_FREE(op->customdata);
+}
+
+static int poselib_blend_exit(bContext *C, wmOperator *op)
+{
+ PoseBlendData *pbd = op->customdata;
+ const ePoseBlendState exit_state = pbd->state;
+
+ poselib_blend_cleanup(C, op);
+ poselib_blend_free(op);
+
+ if (exit_state == POSE_BLEND_CANCEL) {
+ return OPERATOR_CANCELLED;
+ }
+ return OPERATOR_FINISHED;
+}
+
+/* Cancel previewing operation (called when exiting Blender) */
+static void poselib_blend_cancel(bContext *C, wmOperator *op)
+{
+ PoseBlendData *pbd = op->customdata;
+ pbd->state = POSE_BLEND_CANCEL;
+ poselib_blend_exit(C, op);
+}
+
+/* Main modal status check. */
+static int poselib_blend_modal(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ const int operator_result = poselib_blend_handle_event(C, op, event);
+
+ poselib_blend_cursor_update(C, op);
+
+ const PoseBlendData *pbd = op->customdata;
+ if (ELEM(pbd->state, POSE_BLEND_CONFIRM, POSE_BLEND_CANCEL)) {
+ return poselib_blend_exit(C, op);
+ }
+
+ if (pbd->needs_redraw) {
+ poselib_blend_apply(C, op);
+ }
+
+ return operator_result;
+}
+
+/* Modal Operator init. */
+static int poselib_blend_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ if (!poselib_blend_init_data(C, op, event)) {
+ poselib_blend_free(op);
+ return OPERATOR_CANCELLED;
+ }
+
+ /* Do initial apply to have something to look at. */
+ poselib_blend_apply(C, op);
+
+ WM_event_add_modal_handler(C, op);
+ return OPERATOR_RUNNING_MODAL;
+}
+
+/* Single-shot apply. */
+static int poselib_blend_exec(bContext *C, wmOperator *op)
+{
+ if (!poselib_blend_init_data(C, op, NULL)) {
+ poselib_blend_free(op);
+ return OPERATOR_CANCELLED;
+ }
+
+ poselib_blend_apply(C, op);
+
+ PoseBlendData *pbd = op->customdata;
+ pbd->state = POSE_BLEND_CONFIRM;
+ return poselib_blend_exit(C, op);
+}
+
+static bool poselib_asset_in_context(bContext *C)
+{
+ bool asset_handle_valid;
+ /* Check whether the context provides the asset data needed to add a pose. */
+ const AssetLibraryReference *asset_library = CTX_wm_asset_library(C);
+ AssetHandle asset_handle = CTX_wm_asset_handle(C, &asset_handle_valid);
+
+ return (asset_library != NULL) && asset_handle_valid &&
+ (asset_handle.file_data->blentype == ID_AC);
+}
+
+/* Poll callback for operators that require existing PoseLib data (with poses) to work. */
+static bool poselib_blend_poll(bContext *C)
+{
+ Object *ob = get_poselib_object(C);
+ if (ELEM(NULL, ob, ob->pose, ob->data)) {
+ /* Pose lib is only for armatures in pose mode. */
+ return false;
+ }
+
+ return poselib_asset_in_context(C);
+}
+
+void POSELIB_OT_apply_pose_asset(wmOperatorType *ot)
+{
+ /* Identifiers: */
+ ot->name = "Apply Pose Library Pose";
+ ot->idname = "POSELIB_OT_apply_pose_asset";
+ ot->description = "Apply the given Pose Action to the rig";
+
+ /* Callbacks: */
+ ot->exec = poselib_blend_exec;
+ ot->poll = poselib_blend_poll;
+
+ /* Flags: */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* Properties: */
+ RNA_def_float_factor(ot->srna,
+ "blend_factor",
+ 1.0f,
+ 0.0f,
+ 1.0f,
+ "Blend Factor",
+ "Amount that the pose is applied on top of the existing poses",
+ 0.0f,
+ 1.0f);
+ RNA_def_boolean(ot->srna,
+ "flipped",
+ false,
+ "Apply Flipped",
+ "When enabled, applies the pose flipped over the X-axis");
+}
+
+void POSELIB_OT_blend_pose_asset(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ /* Identifiers: */
+ ot->name = "Blend Pose Library Pose";
+ ot->idname = "POSELIB_OT_blend_pose_asset";
+ ot->description = "Blend the given Pose Action to the rig";
+
+ /* Callbacks: */
+ ot->invoke = poselib_blend_invoke;
+ ot->modal = poselib_blend_modal;
+ ot->cancel = poselib_blend_cancel;
+ ot->exec = poselib_blend_exec;
+ ot->poll = poselib_blend_poll;
+
+ /* Flags: */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;
+
+ /* Properties: */
+ prop = RNA_def_float_factor(ot->srna,
+ "blend_factor",
+ 0.0f,
+ 0.0f,
+ 1.0f,
+ "Blend Factor",
+ "Amount that the pose is applied on top of the existing poses",
+ 0.0f,
+ 1.0f);
+ /* Blending should always start at 0%, and not at whatever percentage was last used. This RNA
+ * property just exists for symmetry with the Apply operator (and thus simplicity of the rest of
+ * the code, which can assume this property exists). */
+ RNA_def_property_flag(prop, PROP_SKIP_SAVE);
+
+ RNA_def_boolean(ot->srna,
+ "flipped",
+ false,
+ "Apply Flipped",
+ "When enabled, applies the pose flipped over the X-axis");
+ prop = RNA_def_boolean(ot->srna,
+ "release_confirm",
+ false,
+ "Confirm on Release",
+ "Always confirm operation when releasing button");
+ RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
+}
diff --git a/source/blender/editors/include/ED_armature.h b/source/blender/editors/include/ED_armature.h
index 85563b76f38..eaa54f66928 100644
--- a/source/blender/editors/include/ED_armature.h
+++ b/source/blender/editors/include/ED_armature.h
@@ -23,10 +23,15 @@
#pragma once
+#include <stdbool.h>
+
+#include "BLI_listbase.h"
+
#ifdef __cplusplus
extern "C" {
#endif
+struct bAction;
struct Base;
struct Bone;
struct Depsgraph;
@@ -242,6 +247,17 @@ void ED_mesh_deform_bind_callback(struct MeshDeformModifierData *mmd,
int totvert,
float cagemat[4][4]);
+/* Pose backups, pose_backup.c */
+struct PoseBackup;
+/* Create a backup of those bones that are animated in the given action. */
+struct PoseBackup *ED_pose_backup_create_selected_bones(
+ const struct Object *ob, const struct bAction *action) ATTR_WARN_UNUSED_RESULT;
+struct PoseBackup *ED_pose_backup_create_all_bones(
+ const struct Object *ob, const struct bAction *action) ATTR_WARN_UNUSED_RESULT;
+bool ED_pose_backup_is_selection_relevant(const struct PoseBackup *pose_backup);
+void ED_pose_backup_restore(const struct PoseBackup *pose_backup);
+void ED_pose_backup_free(struct PoseBackup *pose_backup);
+
#ifdef __cplusplus
}
#endif