Anim: add RNA function Action.fcurve_ensure_for_datablock(...)
Expose the convenience function `blender::animrig::action_fcurve_ensure()`
to RNA as `Action.fcurve_ensure_for_datablock(...)`.
The function requires that the Action is already assigned to the
data-block. It then takes care of slot assignment / creation, as well as
the creation of a layer and a keyframe strip.
This function call:
```python
fcurve = action.fcurve_ensure_for_datablock(ob_cube, "location", index=2)
```
effectively performs this logic:
```python
# Ensure the slot exists and is assigned:
slot = ob_cube.animation_data.slot
if not slot:
slot = find_slot_for_keying(action)
if not slot:
slot = action.slots.new(ob_cube.name)
ob_cube.animation_data.slot = slot
# Ensure a layer exists:
if action.layers:
layer = action.layers[0]
else:
layer = action.layers.new("Layer")
# Ensure a keyframe strip exists:
if layer.strips:
strip = layer.strips[0]
else:
strip = layer.strips.new('KEYFRAME')
# Ensure the channelbag exists:
channelbag = strip.channelbag(slot, ensure=True)
# Ensure the F-Curve exists:
fcurve = channelbag.fcurves.find("location", index=1)
if not fcurve:
fcurve = channelbag.fcurves.new("location", index=1)
```
Here `find_slot_for_keying()` represents the logic that's also used when
creating keys via the user interface or the `bpy_struct.keyframe_insert()`
function.
Pull Request: https://projects.blender.org/blender/blender/pulls/134686
This commit is contained in:
@@ -1625,6 +1625,29 @@ animrig::Channelbag *channelbag_for_action_slot(Action &action, slot_handle_t sl
|
||||
Span<FCurve *> fcurves_for_action_slot(Action &action, slot_handle_t slot_handle);
|
||||
Span<const FCurve *> fcurves_for_action_slot(const Action &action, slot_handle_t slot_handle);
|
||||
|
||||
/**
|
||||
* Find or create an F-Curve on the given action that matches the given fcurve
|
||||
* descriptor.
|
||||
*
|
||||
* \note This function also ensures that dependency graph relationships are
|
||||
* rebuilt. This is necessary when adding a new F-Curve, as a
|
||||
* previously-unanimated depsgraph component may become animated now.
|
||||
*
|
||||
* \param action: MUST already be assigned to the animated ID.
|
||||
*
|
||||
* \param animated_id: The ID that is animated by this Action. It is used to
|
||||
* create and assign an appropriate slot if needed when creating the fcurve, and
|
||||
* set the fcurve color properly
|
||||
*
|
||||
* \param fcurve_descriptor: description of the fcurve to lookup/create. Note
|
||||
* that this is *not* relative to `ptr` (e.g. if `ptr` is not an ID). It should
|
||||
* contain the exact data path of the fcurve to be looked up/created.
|
||||
*/
|
||||
FCurve *action_fcurve_ensure(Main *bmain,
|
||||
bAction &action,
|
||||
ID &animated_id,
|
||||
FCurveDescriptor fcurve_descriptor);
|
||||
|
||||
/**
|
||||
* Find or create an F-Curve on the given action that matches the given fcurve
|
||||
* descriptor.
|
||||
@@ -1646,12 +1669,15 @@ Span<const FCurve *> fcurves_for_action_slot(const Action &action, slot_handle_t
|
||||
* \param fcurve_descriptor: description of the fcurve to lookup/create. Note
|
||||
* that this is *not* relative to `ptr` (e.g. if `ptr` is not an ID). It should
|
||||
* contain the exact data path of the fcurve to be looked up/created.
|
||||
*
|
||||
* \see action_fcurve_ensure for a function that is specific to layered actions,
|
||||
* and is easier to use because it does not depend on an RNA pointer.
|
||||
*/
|
||||
FCurve *action_fcurve_ensure(Main *bmain,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
PointerRNA *ptr,
|
||||
FCurveDescriptor fcurve_descriptor);
|
||||
FCurve *action_fcurve_ensure_ex(Main *bmain,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
PointerRNA *ptr,
|
||||
FCurveDescriptor fcurve_descriptor);
|
||||
|
||||
/**
|
||||
* Same as above, but creates a legacy Action.
|
||||
|
||||
@@ -2595,11 +2595,11 @@ Vector<FCurve *> fcurves_in_listbase_filtered(ListBase /* FCurve * */ fcurves,
|
||||
return found;
|
||||
}
|
||||
|
||||
FCurve *action_fcurve_ensure(Main *bmain,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
PointerRNA *ptr,
|
||||
FCurveDescriptor fcurve_descriptor)
|
||||
FCurve *action_fcurve_ensure_ex(Main *bmain,
|
||||
bAction *act,
|
||||
const char group[],
|
||||
PointerRNA *ptr,
|
||||
FCurveDescriptor fcurve_descriptor)
|
||||
{
|
||||
if (act == nullptr) {
|
||||
return nullptr;
|
||||
@@ -2625,13 +2625,21 @@ FCurve *action_fcurve_ensure(Main *bmain,
|
||||
* hold, or if this is even the best place to handle the layered action
|
||||
* cases at all, was leading to discussion of larger changes than made sense
|
||||
* to tackle at that point. */
|
||||
Action &action = act->wrap();
|
||||
|
||||
BLI_assert(ptr != nullptr);
|
||||
if (ptr == nullptr || ptr->owner_id == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
ID &animated_id = *ptr->owner_id;
|
||||
|
||||
return action_fcurve_ensure(bmain, *act, *ptr->owner_id, fcurve_descriptor);
|
||||
}
|
||||
|
||||
FCurve *action_fcurve_ensure(Main *bmain,
|
||||
bAction &dna_action,
|
||||
ID &animated_id,
|
||||
FCurveDescriptor fcurve_descriptor)
|
||||
{
|
||||
Action &action = dna_action.wrap();
|
||||
|
||||
BLI_assert(get_action(animated_id) == &action);
|
||||
if (get_action(animated_id) != &action) {
|
||||
return nullptr;
|
||||
@@ -2640,7 +2648,9 @@ FCurve *action_fcurve_ensure(Main *bmain,
|
||||
/* Ensure the id has an assigned slot. */
|
||||
Slot *slot = assign_action_ensure_slot_for_keying(action, animated_id);
|
||||
if (!slot) {
|
||||
/* This means the ID type is not animatable. */
|
||||
/* This means the ID type is not animatable. How did an Action get assigned
|
||||
* in the first place? */
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
@@ -1208,11 +1208,11 @@ TEST_F(ActionLayersTest, action_move_slot)
|
||||
PointerRNA cube_rna_pointer = RNA_id_pointer_create(&cube->id);
|
||||
PointerRNA suzanne_rna_pointer = RNA_id_pointer_create(&suzanne->id);
|
||||
|
||||
action_fcurve_ensure(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
|
||||
action_fcurve_ensure(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
|
||||
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"location", 0});
|
||||
action_fcurve_ensure_ex(bmain, action, "Test", &cube_rna_pointer, {"rotation_euler", 1});
|
||||
|
||||
action_fcurve_ensure(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"location", 0});
|
||||
action_fcurve_ensure(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"rotation_euler", 1});
|
||||
action_fcurve_ensure_ex(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"location", 0});
|
||||
action_fcurve_ensure_ex(bmain, action_2, "Test_2", &suzanne_rna_pointer, {"rotation_euler", 1});
|
||||
|
||||
ASSERT_EQ(action->layer_array_num, 1);
|
||||
ASSERT_EQ(action_2->layer_array_num, 1);
|
||||
|
||||
@@ -607,7 +607,7 @@ static SingleKeyingResult insert_keyframe_fcurve_value(Main *bmain,
|
||||
*/
|
||||
|
||||
FCurve *fcu = key_insertion_may_create_fcurve(flag) ?
|
||||
action_fcurve_ensure(bmain, act, group, ptr, {rna_path, array_index}) :
|
||||
action_fcurve_ensure_ex(bmain, act, group, ptr, {rna_path, array_index}) :
|
||||
fcurve_find_in_action(act, {rna_path, array_index});
|
||||
|
||||
/* We may not have a F-Curve when we're replacing only. */
|
||||
|
||||
@@ -111,7 +111,7 @@ class CopyDriversToSelected : public testing::Test {
|
||||
/* Add animation to cube's fourth quaternion element. */
|
||||
PointerRNA cube_ptr = RNA_pointer_create_discrete(&cube->id, &RNA_Object, &cube->id);
|
||||
bAction *act = animrig::id_action_ensure(bmain, &cube->id);
|
||||
FCurve *fcu = animrig::action_fcurve_ensure(
|
||||
FCurve *fcu = animrig::action_fcurve_ensure_ex(
|
||||
bmain, act, "Object Transforms", &cube_ptr, {"rotation_quaternion", 3});
|
||||
animrig::KeyframeSettings keyframe_settings = {BEZT_KEYTYPE_KEYFRAME, HD_AUTO, BEZT_IPO_BEZ};
|
||||
insert_vert_fcurve(fcu, {1.0, 1.0}, keyframe_settings, INSERTKEY_NOFLAGS);
|
||||
|
||||
@@ -1090,7 +1090,7 @@ static int followpath_path_animate_exec(bContext *C, wmOperator *op)
|
||||
/* create F-Curve for path animation */
|
||||
act = animrig::id_action_ensure(bmain, &cu->id);
|
||||
PointerRNA id_ptr = RNA_id_pointer_create(&cu->id);
|
||||
fcu = animrig::action_fcurve_ensure(bmain, act, nullptr, &id_ptr, {"eval_time", 0});
|
||||
fcu = animrig::action_fcurve_ensure_ex(bmain, act, nullptr, &id_ptr, {"eval_time", 0});
|
||||
|
||||
/* standard vertical range - 1:1 = 100 frames */
|
||||
standardRange = 100.0f;
|
||||
@@ -1115,7 +1115,7 @@ static int followpath_path_animate_exec(bContext *C, wmOperator *op)
|
||||
/* create F-Curve for constraint */
|
||||
act = animrig::id_action_ensure(bmain, &ob->id);
|
||||
PointerRNA id_ptr = RNA_id_pointer_create(&ob->id);
|
||||
fcu = animrig::action_fcurve_ensure(bmain, act, nullptr, &id_ptr, {path->c_str(), 0});
|
||||
fcu = animrig::action_fcurve_ensure_ex(bmain, act, nullptr, &id_ptr, {path->c_str(), 0});
|
||||
|
||||
/* standard vertical range - 0.0 to 1.0 */
|
||||
standardRange = 1.0f;
|
||||
|
||||
@@ -546,7 +546,7 @@ bool parent_set(ReportList *reports,
|
||||
/* get or create F-Curve */
|
||||
bAction *act = animrig::id_action_ensure(bmain, &cu->id);
|
||||
PointerRNA id_ptr = RNA_id_pointer_create(&cu->id);
|
||||
FCurve *fcu = animrig::action_fcurve_ensure(
|
||||
FCurve *fcu = animrig::action_fcurve_ensure_ex(
|
||||
bmain, act, nullptr, &id_ptr, {"eval_time", 0});
|
||||
|
||||
/* setup dummy 'generator' modifier here to get 1-1 correspondence still working */
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "BKE_action.hh"
|
||||
#include "BKE_blender.hh"
|
||||
#include "BKE_report.hh"
|
||||
|
||||
#include "RNA_access.hh"
|
||||
#include "RNA_define.hh"
|
||||
@@ -1369,6 +1370,44 @@ static void rna_Action_deselect_keys(bAction *act)
|
||||
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
||||
}
|
||||
|
||||
static FCurve *rna_Action_fcurve_ensure_for_datablock(bAction *_self,
|
||||
Main *bmain,
|
||||
ReportList *reports,
|
||||
ID *datablock,
|
||||
const char *data_path,
|
||||
const int array_index)
|
||||
{
|
||||
/* Precondition checks. */
|
||||
{
|
||||
if (blender::animrig::get_action(*datablock) != _self) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR_INVALID_INPUT,
|
||||
"Assign action \"%s\" to \"%s\" before calling this function",
|
||||
_self->id.name + 2,
|
||||
datablock->name + 2);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BLI_assert(data_path != nullptr);
|
||||
if (data_path[0] == '\0') {
|
||||
BKE_report(reports, RPT_ERROR_INVALID_INPUT, "F-Curve data path empty, invalid argument");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
FCurve *fcurve = blender::animrig::action_fcurve_ensure(
|
||||
bmain, *_self, *datablock, {data_path, array_index});
|
||||
|
||||
if (!fcurve) {
|
||||
/* This should never happen, given the precondition check above. */
|
||||
BLI_assert_unreachable();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WM_main_add_notifier(NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
||||
return fcurve;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to check if an action (value pointer)
|
||||
* is suitable to be assigned to the ID-block that is ptr.
|
||||
@@ -2847,6 +2886,9 @@ static void rna_def_action(BlenderRNA *brna)
|
||||
StructRNA *srna;
|
||||
PropertyRNA *prop;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
srna = RNA_def_struct(brna, "Action", "ID");
|
||||
RNA_def_struct_sdna(srna, "bAction");
|
||||
RNA_def_struct_ui_text(srna, "Action", "A collection of F-Curves for animation");
|
||||
@@ -2987,10 +3029,34 @@ static void rna_def_action(BlenderRNA *brna)
|
||||
RNA_def_property_float_funcs(prop, "rna_Action_curve_frame_range_get", nullptr, nullptr);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
|
||||
FunctionRNA *func = RNA_def_function(srna, "deselect_keys", "rna_Action_deselect_keys");
|
||||
func = RNA_def_function(srna, "deselect_keys", "rna_Action_deselect_keys");
|
||||
RNA_def_function_ui_description(
|
||||
func, "Deselects all keys of the Action. The selection status of F-Curves is unchanged.");
|
||||
|
||||
/* action.fcurve_ensure_for_datablock() */
|
||||
func = RNA_def_function(
|
||||
srna, "fcurve_ensure_for_datablock", "rna_Action_fcurve_ensure_for_datablock");
|
||||
RNA_def_function_ui_description(
|
||||
func,
|
||||
"Ensure that an F-Curve exists, with the given data path and array index, for the given "
|
||||
"data-block. This action must already be assigned to the data-block. This function will "
|
||||
"also create the layer, keyframe strip, and action slot if necessary, and take care of "
|
||||
"assigning the action slot too");
|
||||
RNA_def_function_flag(func, FUNC_USE_MAIN | FUNC_USE_REPORTS);
|
||||
|
||||
parm = RNA_def_pointer(func,
|
||||
"datablock",
|
||||
"ID",
|
||||
"",
|
||||
"The data-block animated by this action, for which to ensure the F-Curve "
|
||||
"exists. This action must already be assigned to the data-block");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
|
||||
parm = RNA_def_string(func, "data_path", nullptr, 0, "Data Path", "F-Curve data path");
|
||||
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||
RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Array index", 0, INT_MAX);
|
||||
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "The found or created F-Curve");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
rna_def_action_legacy(brna, srna);
|
||||
|
||||
/* API calls */
|
||||
|
||||
@@ -913,6 +913,48 @@ class SlotHandleLibraryOverridesTest(unittest.TestCase):
|
||||
-1, "Suzanne should be significantly below Z=0 when animated by the library Action")
|
||||
|
||||
|
||||
class ConvenienceFunctionsTest(unittest.TestCase):
|
||||
|
||||
def setUp(self) -> None:
|
||||
bpy.ops.wm.read_homefile(use_factory_startup=True)
|
||||
|
||||
self.action = bpy.data.actions.new('Action')
|
||||
|
||||
def test_fcurve_ensure_for_datablock(self) -> None:
|
||||
# The function should be None-safe.
|
||||
with self.assertRaises(TypeError):
|
||||
self.action.fcurve_ensure_for_datablock(None, "location")
|
||||
self.assertEqual(0, len(self.action.layers))
|
||||
self.assertEqual(0, len(self.action.slots))
|
||||
|
||||
# The function should not work unless the Action is assigned to its target.
|
||||
ob_cube = bpy.data.objects["Cube"]
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.action.fcurve_ensure_for_datablock(ob_cube, "location")
|
||||
self.assertEqual(0, len(self.action.layers))
|
||||
self.assertEqual(0, len(self.action.slots))
|
||||
|
||||
# The function should not work on empty data paths.
|
||||
adt = ob_cube.animation_data_create()
|
||||
adt.action = self.action
|
||||
with self.assertRaises(RuntimeError):
|
||||
self.action.fcurve_ensure_for_datablock(ob_cube, "")
|
||||
self.assertEqual(0, len(self.action.layers))
|
||||
self.assertEqual(0, len(self.action.slots))
|
||||
|
||||
# And finally the happy flow.
|
||||
fcurve = self.action.fcurve_ensure_for_datablock(ob_cube, "location", index=2)
|
||||
self.assertEqual(1, len(self.action.layers))
|
||||
self.assertEqual(1, len(self.action.layers[0].strips))
|
||||
self.assertEqual('KEYFRAME', self.action.layers[0].strips[0].type)
|
||||
self.assertEqual(1, len(self.action.slots))
|
||||
self.assertEqual("location", fcurve.data_path)
|
||||
self.assertEqual(2, fcurve.array_index)
|
||||
|
||||
channelbag = self.action.layers[0].strips[0].channelbags[0]
|
||||
self.assertEqual(fcurve, channelbag.fcurves[0])
|
||||
|
||||
|
||||
def main():
|
||||
global args
|
||||
import argparse
|
||||
|
||||
Reference in New Issue
Block a user