Anim: ChannelBag F-Curve management functions (C++/RNA)
Add F-Curve management functions on ChannelBags
(`channelbag.fcurves.xxx`) that are very similar to the legacy Action
functions `Action.fcurves.xxx`.
```python
channelbag = strip.channelbags.new(slot)
fcurve = channelbag.fcurves.new("rotation_quaternion", index=1)
assert channelbag.fcurves[0] == fcurve
channelbag.fcurves.remove(fcurve)
channelbag.fcurves.clear()
```
Pull Request: https://projects.blender.org/blender/blender/pulls/124987
This commit is contained in:
@@ -715,6 +715,36 @@ class ChannelBag : public ::ActionChannelBag {
|
||||
* exist.
|
||||
*/
|
||||
FCurve &fcurve_ensure(FCurveDescriptor fcurve_descriptor);
|
||||
|
||||
/**
|
||||
* Create an F-Curve, but only if it doesn't exist yet in this ChannelBag.
|
||||
*
|
||||
* \return the F-Curve it it was created, or nullptr if it already existed.
|
||||
*/
|
||||
FCurve *fcurve_create_unique(FCurveDescriptor fcurve_descriptor);
|
||||
|
||||
/**
|
||||
* Remove an F-Curve from the ChannelBag.
|
||||
*
|
||||
* After this call, if the F-Curve was found, the reference will no longer be
|
||||
* valid, as the curve will have been freed.
|
||||
*
|
||||
* \return true when the F-Curve was found & removed, false if it wasn't found.
|
||||
*/
|
||||
bool fcurve_remove(FCurve &fcurve_to_remove);
|
||||
|
||||
/**
|
||||
* Remove all F-Curves from this ChannelBag.
|
||||
*/
|
||||
void fcurves_clear();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Create an F-Curve.
|
||||
*
|
||||
* Assumes that there is no such F-Curve yet on this ChannelBag.
|
||||
*/
|
||||
FCurve &fcurve_create(FCurveDescriptor fcurve_descriptor);
|
||||
};
|
||||
static_assert(sizeof(ChannelBag) == sizeof(::ActionChannelBag),
|
||||
"DNA struct and its C++ wrapper must have the same size");
|
||||
|
||||
@@ -1129,7 +1129,19 @@ FCurve &ChannelBag::fcurve_ensure(const FCurveDescriptor fcurve_descriptor)
|
||||
if (FCurve *existing_fcurve = this->fcurve_find(fcurve_descriptor)) {
|
||||
return *existing_fcurve;
|
||||
}
|
||||
return this->fcurve_create(fcurve_descriptor);
|
||||
}
|
||||
|
||||
FCurve *ChannelBag::fcurve_create_unique(FCurveDescriptor fcurve_descriptor)
|
||||
{
|
||||
if (this->fcurve_find(fcurve_descriptor)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &this->fcurve_create(fcurve_descriptor);
|
||||
}
|
||||
|
||||
FCurve &ChannelBag::fcurve_create(FCurveDescriptor fcurve_descriptor)
|
||||
{
|
||||
FCurve *new_fcurve = create_fcurve_for_channel(fcurve_descriptor);
|
||||
|
||||
if (this->fcurve_array_num == 0) {
|
||||
@@ -1140,6 +1152,29 @@ FCurve &ChannelBag::fcurve_ensure(const FCurveDescriptor fcurve_descriptor)
|
||||
return *new_fcurve;
|
||||
}
|
||||
|
||||
static void fcurve_ptr_destructor(FCurve **fcurve_ptr)
|
||||
{
|
||||
BKE_fcurve_free(*fcurve_ptr);
|
||||
};
|
||||
|
||||
bool ChannelBag::fcurve_remove(FCurve &fcurve_to_remove)
|
||||
{
|
||||
const int64_t fcurve_index = this->fcurves().as_span().first_index_try(&fcurve_to_remove);
|
||||
if (fcurve_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dna::array::remove_index(
|
||||
&this->fcurve_array, &this->fcurve_array_num, nullptr, fcurve_index, fcurve_ptr_destructor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChannelBag::fcurves_clear()
|
||||
{
|
||||
dna::array::clear(&this->fcurve_array, &this->fcurve_array_num, nullptr, fcurve_ptr_destructor);
|
||||
}
|
||||
|
||||
SingleKeyingResult KeyframeStrip::keyframe_insert(const Slot &slot,
|
||||
const FCurveDescriptor fcurve_descriptor,
|
||||
const float2 time_value,
|
||||
|
||||
@@ -520,7 +520,7 @@ static bool rna_KeyframeActionStrip_key_insert(ID *id,
|
||||
return ok;
|
||||
}
|
||||
|
||||
static std::optional<std::string> rna_ActionChannelBag_path(const PointerRNA *ptr)
|
||||
static std::optional<std::string> rna_ChannelBag_path(const PointerRNA *ptr)
|
||||
{
|
||||
animrig::Action &action = rna_action(ptr);
|
||||
animrig::ChannelBag &cbag_to_find = rna_data_channelbag(ptr);
|
||||
@@ -561,6 +561,71 @@ static int rna_iterator_ChannelBag_fcurves_length(PointerRNA *ptr)
|
||||
return bag.fcurves().size();
|
||||
}
|
||||
|
||||
static FCurve *rna_ChannelBag_fcurve_new(ActionChannelBag *dna_channelbag,
|
||||
ReportList *reports,
|
||||
const char *data_path,
|
||||
const int index)
|
||||
{
|
||||
BLI_assert(data_path != nullptr);
|
||||
if (data_path[0] == '\0') {
|
||||
BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
animrig::ChannelBag &self = dna_channelbag->wrap();
|
||||
FCurve *fcurve = self.fcurve_create_unique({data_path, index});
|
||||
if (!fcurve) {
|
||||
BKE_reportf(reports,
|
||||
RPT_ERROR,
|
||||
"F-Curve '%s[%d]' already exists in this channelbag",
|
||||
data_path,
|
||||
index);
|
||||
return nullptr;
|
||||
}
|
||||
return fcurve;
|
||||
}
|
||||
|
||||
static FCurve *rna_ChannelBag_fcurve_find(ActionChannelBag *dna_channelbag,
|
||||
ReportList *reports,
|
||||
const char *data_path,
|
||||
const int index)
|
||||
{
|
||||
if (data_path[0] == '\0') {
|
||||
BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
animrig::ChannelBag &self = dna_channelbag->wrap();
|
||||
return self.fcurve_find({data_path, index});
|
||||
}
|
||||
|
||||
static void rna_ChannelBag_fcurve_remove(ID *dna_action_id,
|
||||
ActionChannelBag *dna_channelbag,
|
||||
bContext *C,
|
||||
ReportList *reports,
|
||||
PointerRNA *fcurve_ptr)
|
||||
{
|
||||
animrig::ChannelBag &self = dna_channelbag->wrap();
|
||||
FCurve *fcurve = static_cast<FCurve *>(fcurve_ptr->data);
|
||||
|
||||
if (!self.fcurve_remove(*fcurve)) {
|
||||
BKE_reportf(reports, RPT_ERROR, "F-Curve not found");
|
||||
return;
|
||||
}
|
||||
|
||||
DEG_id_tag_update(dna_action_id, ID_RECALC_ANIMATION_NO_FLUSH);
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
||||
}
|
||||
|
||||
static void rna_ChannelBag_fcurve_clear(ID *dna_action_id,
|
||||
ActionChannelBag *dna_channelbag,
|
||||
bContext *C)
|
||||
{
|
||||
dna_channelbag->wrap().fcurves_clear();
|
||||
DEG_id_tag_update(dna_action_id, ID_RECALC_ANIMATION_NO_FLUSH);
|
||||
WM_event_add_notifier(C, NC_ANIMATION | ND_KEYFRAME | NA_EDITED, nullptr);
|
||||
}
|
||||
|
||||
static ActionChannelBag *rna_KeyframeActionStrip_channels(KeyframeActionStrip *self,
|
||||
const animrig::slot_handle_t slot_handle)
|
||||
{
|
||||
@@ -1667,14 +1732,63 @@ static void rna_def_action_strip(BlenderRNA *brna)
|
||||
rna_def_action_keyframe_strip(brna);
|
||||
}
|
||||
|
||||
static void rna_def_channelbag_for_slot_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
static void rna_def_channelbag_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
|
||||
{
|
||||
StructRNA *srna;
|
||||
|
||||
FunctionRNA *func;
|
||||
PropertyRNA *parm;
|
||||
|
||||
RNA_def_property_srna(cprop, "ActionChannelBagFCurves");
|
||||
srna = RNA_def_struct(brna, "ActionChannelBagFCurves", nullptr);
|
||||
RNA_def_struct_sdna(srna, "bActionChannelBag");
|
||||
RNA_def_struct_ui_text(srna, "F-Curves", "Collection of F-Curves for a specific action slot");
|
||||
RNA_def_struct_sdna(srna, "ActionChannelBag");
|
||||
RNA_def_struct_ui_text(
|
||||
srna, "F-Curves", "Collection of F-Curves for a specific action slot, on a specific strip");
|
||||
|
||||
/* ChannelBag.fcurves.new(...) */
|
||||
extern struct FCurve *ActionChannelBagFCurves_new_func(struct ID * _selfid,
|
||||
struct ActionChannelBag * _self,
|
||||
Main * bmain,
|
||||
ReportList * reports,
|
||||
const char *data_path,
|
||||
int index);
|
||||
|
||||
func = RNA_def_function(srna, "new", "rna_ChannelBag_fcurve_new");
|
||||
RNA_def_function_ui_description(func, "Add an F-Curve to the channelbag");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
parm = RNA_def_string(func, "data_path", nullptr, 0, "Data Path", "F-Curve data path to use");
|
||||
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", "", "Newly created F-Curve");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
/* ChannelBag.fcurves.find(...) */
|
||||
func = RNA_def_function(srna, "find", "rna_ChannelBag_fcurve_find");
|
||||
RNA_def_function_ui_description(
|
||||
func,
|
||||
"Find an F-Curve. Note that this function performs a linear scan "
|
||||
"of all F-Curves in the channelbag.");
|
||||
RNA_def_function_flag(func, FUNC_USE_REPORTS);
|
||||
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 F-Curve, or None if it doesn't exist");
|
||||
RNA_def_function_return(func, parm);
|
||||
|
||||
/* ChannelBag.fcurves.remove(...) */
|
||||
func = RNA_def_function(srna, "remove", "rna_ChannelBag_fcurve_remove");
|
||||
RNA_def_function_ui_description(func, "Remove F-Curve");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_SELF_ID | FUNC_USE_REPORTS);
|
||||
parm = RNA_def_pointer(func, "fcurve", "FCurve", "", "F-Curve to remove");
|
||||
RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
|
||||
RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, ParameterFlag(0));
|
||||
|
||||
/* ChannelBag.fcurves.clear() */
|
||||
func = RNA_def_function(srna, "clear", "rna_ChannelBag_fcurve_clear");
|
||||
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_SELF_ID);
|
||||
RNA_def_function_ui_description(func, "Remove all F-Curves from this channelbag");
|
||||
}
|
||||
|
||||
static void rna_def_action_channelbag(BlenderRNA *brna)
|
||||
@@ -1687,7 +1801,7 @@ static void rna_def_action_channelbag(BlenderRNA *brna)
|
||||
srna,
|
||||
"Animation Channel Bag",
|
||||
"Collection of animation channels, typically associated with an action slot");
|
||||
RNA_def_struct_path_func(srna, "rna_ActionChannelBag_path");
|
||||
RNA_def_struct_path_func(srna, "rna_ChannelBag_path");
|
||||
|
||||
prop = RNA_def_property(srna, "slot_handle", PROP_INT, PROP_NONE);
|
||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||
@@ -1704,7 +1818,7 @@ static void rna_def_action_channelbag(BlenderRNA *brna)
|
||||
nullptr);
|
||||
RNA_def_property_struct_type(prop, "FCurve");
|
||||
RNA_def_property_ui_text(prop, "F-Curves", "The individual F-Curves that animate the slot");
|
||||
rna_def_channelbag_for_slot_fcurves(brna, prop);
|
||||
rna_def_channelbag_fcurves(brna, prop);
|
||||
}
|
||||
# endif // WITH_ANIM_BAKLAVA
|
||||
|
||||
|
||||
@@ -170,22 +170,66 @@ class ChannelBagsTest(unittest.TestCase):
|
||||
while anims:
|
||||
anims.remove(anims[0])
|
||||
|
||||
self.action = bpy.data.actions.new('TestAction')
|
||||
|
||||
self.slot = self.action.slots.new()
|
||||
self.slot.name = 'OBTest'
|
||||
|
||||
self.layer = self.action.layers.new(name="Layer")
|
||||
self.strip = self.layer.strips.new(type='KEYFRAME')
|
||||
|
||||
def test_create_remove_channelbag(self):
|
||||
action = bpy.data.actions.new('TestAction')
|
||||
channelbag = self.strip.channelbags.new(self.slot)
|
||||
|
||||
slot = action.slots.new()
|
||||
slot.name = 'OBTest'
|
||||
|
||||
layer = action.layers.new(name="Layer")
|
||||
strip = layer.strips.new(type='KEYFRAME')
|
||||
channelbag = strip.channelbags.new(slot)
|
||||
|
||||
strip.key_insert(slot, "location", 1, 47.0, 327.0)
|
||||
self.strip.key_insert(self.slot, "location", 1, 47.0, 327.0)
|
||||
self.assertEqual("location", channelbag.fcurves[0].data_path,
|
||||
"Keys for the channelbag's slot should go into the channelbag")
|
||||
|
||||
strip.channelbags.remove(channelbag)
|
||||
self.assertEqual([], list(strip.channelbags))
|
||||
self.strip.channelbags.remove(channelbag)
|
||||
self.assertEqual([], list(self.strip.channelbags))
|
||||
|
||||
def test_create_remove_fcurves(self):
|
||||
channelbag = self.strip.channelbags.new(self.slot)
|
||||
|
||||
# Creating an F-Curve should work.
|
||||
fcurve = channelbag.fcurves.new('location', index=1)
|
||||
self.assertIsNotNone(fcurve)
|
||||
self.assertEquals(fcurve.data_path, 'location')
|
||||
self.assertEquals(fcurve.array_index, 1)
|
||||
self.assertEquals([fcurve], channelbag.fcurves[:])
|
||||
|
||||
# Empty data paths should not be accepted.
|
||||
with self.assertRaises(RuntimeError):
|
||||
channelbag.fcurves.new('', index=1)
|
||||
self.assertEquals([fcurve], channelbag.fcurves[:])
|
||||
|
||||
# Creating an F-Curve twice should fail:
|
||||
with self.assertRaises(RuntimeError):
|
||||
channelbag.fcurves.new('location', index=1)
|
||||
self.assertEquals([fcurve], channelbag.fcurves[:])
|
||||
|
||||
# Removing an unrelated F-Curve should fail, even when an F-Curve with
|
||||
# the same RNA path and array index exists.
|
||||
other_slot = self.action.slots.new()
|
||||
other_cbag = self.strip.channelbags.new(other_slot)
|
||||
other_fcurve = other_cbag.fcurves.new('location', index=1)
|
||||
with self.assertRaises(RuntimeError):
|
||||
channelbag.fcurves.remove(other_fcurve)
|
||||
self.assertEquals([fcurve], channelbag.fcurves[:])
|
||||
|
||||
# Removing an existing F-Curve should work:
|
||||
channelbag.fcurves.remove(fcurve)
|
||||
self.assertEquals([], channelbag.fcurves[:])
|
||||
|
||||
def test_fcurves_clear(self):
|
||||
channelbag = self.strip.channelbags.new(self.slot)
|
||||
|
||||
for index in range(4):
|
||||
channelbag.fcurves.new('rotation_quaternion', index=index)
|
||||
|
||||
self.assertEquals(4, len(channelbag.fcurves))
|
||||
channelbag.fcurves.clear()
|
||||
self.assertEquals([], channelbag.fcurves[:])
|
||||
|
||||
|
||||
class DataPathTest(unittest.TestCase):
|
||||
|
||||
Reference in New Issue
Block a user