From b4cfe80547244e69017bb9c8432a4b18c21b5a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 11 Nov 2021 17:51:38 +0100 Subject: Assets: Store Action sub-type in asset metadata Blender 3.0 will only support single-frame Actions in the pose library. The goal of this patch is to lay the groundwork for making it possible for the Asset Browser to reject/hide "animation snippet" Action assets. Determining whether an Action has one or more frames (i.e. whether it has a single pose or animation) requires inspecting the Action itself, and thus loading the data-block itself. This would make it impossible to quickly determine from the asset browser. To solve this, the Action is inspected before saving, and a `"is_single_frame"` boolean (well, 0/1 integer) IDProperty is added. Reviewed by: Severin Differential Revision: https://developer.blender.org/D13202 --- source/blender/blenkernel/intern/action.c | 68 ++++++++++++++++++ source/blender/blenkernel/intern/action_test.cc | 95 +++++++++++++++++++++++++ 2 files changed, 163 insertions(+) (limited to 'source/blender/blenkernel/intern') diff --git a/source/blender/blenkernel/intern/action.c b/source/blender/blenkernel/intern/action.c index 7403b7f2109..842902b51ed 100644 --- a/source/blender/blenkernel/intern/action.c +++ b/source/blender/blenkernel/intern/action.c @@ -51,6 +51,7 @@ #include "BKE_anim_visualization.h" #include "BKE_animsys.h" #include "BKE_armature.h" +#include "BKE_asset.h" #include "BKE_constraint.h" #include "BKE_deform.h" #include "BKE_fcurve.h" @@ -286,6 +287,30 @@ static void action_blend_read_expand(BlendExpander *expander, ID *id) } } +static IDProperty *action_asset_type_property(const bAction *action) +{ + const bool is_single_frame = !BKE_action_has_single_frame(action); + + IDPropertyTemplate idprop = {0}; + idprop.i = is_single_frame; + + IDProperty *property = IDP_New(IDP_INT, &idprop, "is_single_frame"); + return property; +} + +static void action_asset_pre_save(void *asset_ptr, struct AssetMetaData *asset_data) +{ + bAction *action = (bAction *)asset_ptr; + BLI_assert(GS(action->id.name) == ID_AC); + + IDProperty *action_type = action_asset_type_property(action); + BKE_asset_metadata_idprop_ensure(asset_data, action_type); +} + +AssetTypeInfo AssetType_AC = { + /* pre_save_fn */ action_asset_pre_save, +}; + IDTypeInfo IDType_ID_AC = { .id_code = ID_AC, .id_filter = FILTER_ID_AC, @@ -312,6 +337,8 @@ IDTypeInfo IDType_ID_AC = { .blend_read_undo_preserve = NULL, .lib_override_apply_post = NULL, + + .asset_type_info = &AssetType_AC, }; /* ***************** Library data level operations on action ************** */ @@ -1418,6 +1445,47 @@ bool action_has_motion(const bAction *act) return false; } +bool BKE_action_has_single_frame(const struct bAction *act) +{ + if (act == NULL || BLI_listbase_is_empty(&act->curves)) { + return false; + } + + bool found_key = false; + float found_key_frame = 0.0f; + + LISTBASE_FOREACH (FCurve *, fcu, &act->curves) { + switch (fcu->totvert) { + case 0: + /* No keys, so impossible to come to a conclusion on this curve alone. */ + continue; + case 1: + /* Single key, which is the complex case, so handle below. */ + break; + default: + /* Multiple keys, so there is animation. */ + return false; + } + + const float this_key_frame = fcu->bezt != NULL ? fcu->bezt[0].vec[1][0] : fcu->fpt[0].vec[0]; + if (!found_key) { + found_key = true; + found_key_frame = this_key_frame; + continue; + } + + /* The graph editor rounds to 1/1000th of a frame, so it's not necessary to be really precise + * with these comparisons. */ + if (!compare_ff(found_key_frame, this_key_frame, 0.001f)) { + /* This key differs from the already-found key, so this Action represents animation. */ + return false; + } + } + + /* There is only a single frame if we found at least one key. */ + return found_key; +} + /* Calculate the extents of given action */ void calc_action_range(const bAction *act, float *start, float *end, short incl_modifiers) { diff --git a/source/blender/blenkernel/intern/action_test.cc b/source/blender/blenkernel/intern/action_test.cc index c02eca966ad..c0d9a4c6055 100644 --- a/source/blender/blenkernel/intern/action_test.cc +++ b/source/blender/blenkernel/intern/action_test.cc @@ -24,6 +24,8 @@ #include "BLI_listbase.h" +#include "MEM_guardedalloc.h" + #include "testing/testing.h" namespace blender::bke::tests { @@ -141,4 +143,97 @@ TEST(action_groups, ReconstructGroupsWithReordering) EXPECT_EQ(groupDcurve2.next, nullptr); } +namespace { + +/* Allocate fcu->bezt, and also return a unique_ptr to it for easily freeing the memory. */ +std::unique_ptr allocate_keyframes(FCurve *fcu, const size_t num_keyframes) +{ + auto bezt_uptr = std::make_unique(num_keyframes); + fcu->bezt = bezt_uptr.get(); + return bezt_uptr; +} + +/* Append keyframe, assumes that fcu->bezt is allocated and has enough space. */ +void add_keyframe(FCurve *fcu, float x, float y) +{ + /* The insert_keyframe functions are in the editors, so we cannot link to those here. */ + BezTriple the_keyframe; + memset(&the_keyframe, 0, sizeof(the_keyframe)); + + /* Copied from insert_vert_fcurve() in keyframing.c. */ + the_keyframe.vec[0][0] = x - 1.0f; + the_keyframe.vec[0][1] = y; + the_keyframe.vec[1][0] = x; + the_keyframe.vec[1][1] = y; + the_keyframe.vec[2][0] = x + 1.0f; + the_keyframe.vec[2][1] = y; + + memcpy(&fcu->bezt[fcu->totvert], &the_keyframe, sizeof(the_keyframe)); + fcu->totvert++; +} + +} // namespace + +TEST(action_assets, BKE_action_has_single_frame) +{ + /* NULL action. */ + EXPECT_FALSE(BKE_action_has_single_frame(nullptr)) << "NULL Action cannot have a single frame."; + + /* No FCurves. */ + { + const bAction empty = {nullptr}; + EXPECT_FALSE(BKE_action_has_single_frame(&empty)) + << "Action without FCurves cannot have a single frame."; + } + + /* One curve with one key. */ + { + FCurve fcu = {nullptr}; + std::unique_ptr bezt = allocate_keyframes(&fcu, 1); + add_keyframe(&fcu, 1.0f, 2.0f); + + bAction action = {nullptr}; + BLI_addtail(&action.curves, &fcu); + + EXPECT_TRUE(BKE_action_has_single_frame(&action)) + << "Action with one FCurve and one key should have single frame."; + } + + /* Two curves with one key each. */ + { + FCurve fcu1 = {nullptr}; + FCurve fcu2 = {nullptr}; + std::unique_ptr bezt1 = allocate_keyframes(&fcu1, 1); + std::unique_ptr bezt2 = allocate_keyframes(&fcu2, 1); + add_keyframe(&fcu1, 1.0f, 327.0f); + add_keyframe(&fcu2, 1.0f, 47.0f); /* Same X-coordinate as the other one. */ + + bAction action = {nullptr}; + BLI_addtail(&action.curves, &fcu1); + BLI_addtail(&action.curves, &fcu2); + + EXPECT_TRUE(BKE_action_has_single_frame(&action)) + << "Two FCurves with keys on the same frame should have single frame."; + + /* Modify the 2nd curve so it's keyed on a different frame. */ + fcu2.bezt[0].vec[1][0] = 2.0f; + EXPECT_FALSE(BKE_action_has_single_frame(&action)) + << "Two FCurves with keys on different frames should have animation."; + } + + /* One curve with two keys. */ + { + FCurve fcu = {nullptr}; + std::unique_ptr bezt = allocate_keyframes(&fcu, 2); + add_keyframe(&fcu, 1.0f, 2.0f); + add_keyframe(&fcu, 2.0f, 2.5f); + + bAction action = {nullptr}; + BLI_addtail(&action.curves, &fcu); + + EXPECT_FALSE(BKE_action_has_single_frame(&action)) + << "Action with one FCurve and two keys must have animation."; + } +} + } // namespace blender::bke::tests -- cgit v1.2.3