Anim: add RNA code for ChannelBags
RNA API for creating & removing channelbags, as well as a path function to construct RNA paths for channelbags. ```python action = bpy.data.actions.new('TestAction') slot = action.slots.new() slot.name = 'OBTest' layer = action.layers.new(name="Layer") strip = layer.strips.new(type='KEYFRAME') # New in this commit: channelbag = strip.channelbags.new(slot) strip.channelbags.remove(channelbag) ``` Pull Request: https://projects.blender.org/blender/blender/pulls/124793
This commit is contained in:
parent
3ecfa5659e
commit
7360ce4bcc
@ -657,6 +657,19 @@ class KeyframeStrip : public ::KeyframeActionStrip {
|
|||||||
* Should only be called when there is no `ChannelBag` for this slot yet.
|
* Should only be called when there is no `ChannelBag` for this slot yet.
|
||||||
*/
|
*/
|
||||||
ChannelBag &channelbag_for_slot_add(const Slot &slot);
|
ChannelBag &channelbag_for_slot_add(const Slot &slot);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the ChannelBag from this slot.
|
||||||
|
*
|
||||||
|
* After this call the reference is no longer valid, as the memory will have been freed.
|
||||||
|
*
|
||||||
|
* \return true when the ChannelBag was found & removed, false if it wasn't found.
|
||||||
|
*/
|
||||||
|
bool channelbag_remove(ChannelBag &channelbag_to_remove);
|
||||||
|
|
||||||
|
/** Return the channelbag's index, or -1 if there is none for this slot handle. */
|
||||||
|
int64_t find_channelbag_index(const ChannelBag &channelbag) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find an FCurve for this slot + RNA path + array index combination.
|
* Find an FCurve for this slot + RNA path + array index combination.
|
||||||
*
|
*
|
||||||
|
@ -1043,6 +1043,15 @@ const ChannelBag *KeyframeStrip::channelbag_for_slot(const slot_handle_t slot_ha
|
|||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
int64_t KeyframeStrip::find_channelbag_index(const ChannelBag &channelbag_to_remove) const
|
||||||
|
{
|
||||||
|
for (int64_t index = 0; index < this->channelbag_array_num; index++) {
|
||||||
|
if (this->channelbag(index) == &channelbag_to_remove) {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
ChannelBag *KeyframeStrip::channelbag_for_slot(const slot_handle_t slot_handle)
|
ChannelBag *KeyframeStrip::channelbag_for_slot(const slot_handle_t slot_handle)
|
||||||
{
|
{
|
||||||
const auto *const_this = const_cast<const KeyframeStrip *>(this);
|
const auto *const_this = const_cast<const KeyframeStrip *>(this);
|
||||||
@ -1072,6 +1081,28 @@ ChannelBag &KeyframeStrip::channelbag_for_slot_add(const Slot &slot)
|
|||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void channelbag_ptr_destructor(ActionChannelBag **dna_channelbag_ptr)
|
||||||
|
{
|
||||||
|
ChannelBag &channelbag = (*dna_channelbag_ptr)->wrap();
|
||||||
|
MEM_delete(&channelbag);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool KeyframeStrip::channelbag_remove(ChannelBag &channelbag_to_remove)
|
||||||
|
{
|
||||||
|
const int64_t channelbag_index = this->find_channelbag_index(channelbag_to_remove);
|
||||||
|
if (channelbag_index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dna::array::remove_index(&this->channelbag_array,
|
||||||
|
&this->channelbag_array_num,
|
||||||
|
nullptr,
|
||||||
|
channelbag_index,
|
||||||
|
channelbag_ptr_destructor);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
FCurve *KeyframeStrip::fcurve_find(const Slot &slot, const FCurveDescriptor fcurve_descriptor)
|
FCurve *KeyframeStrip::fcurve_find(const Slot &slot, const FCurveDescriptor fcurve_descriptor)
|
||||||
{
|
{
|
||||||
ChannelBag *channels = this->channelbag_for_slot(slot);
|
ChannelBag *channels = this->channelbag_for_slot(slot);
|
||||||
|
@ -451,6 +451,45 @@ static int rna_iterator_keyframestrip_channelbags_length(PointerRNA *ptr)
|
|||||||
return key_strip.channelbags().size();
|
return key_strip.channelbags().size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static ActionChannelBag *rna_ChannelBags_new(KeyframeActionStrip *dna_strip,
|
||||||
|
bContext *C,
|
||||||
|
ReportList *reports,
|
||||||
|
ActionSlot *dna_slot)
|
||||||
|
{
|
||||||
|
animrig::KeyframeStrip &key_strip = dna_strip->wrap();
|
||||||
|
animrig::Slot &slot = dna_slot->wrap();
|
||||||
|
|
||||||
|
if (key_strip.channelbag_for_slot(slot) != nullptr) {
|
||||||
|
BKE_report(reports, RPT_ERROR, "A channelbag for this slot already exists");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelBag &channelbag = key_strip.channelbag_for_slot_add(slot);
|
||||||
|
|
||||||
|
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||||
|
/* No need to tag the depsgraph, as there is no new animation yet. */
|
||||||
|
|
||||||
|
return &channelbag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rna_ChannelBags_remove(ID *action,
|
||||||
|
KeyframeActionStrip *dna_strip,
|
||||||
|
bContext *C,
|
||||||
|
ReportList *reports,
|
||||||
|
ActionChannelBag *dna_channelbag)
|
||||||
|
{
|
||||||
|
animrig::KeyframeStrip &key_strip = dna_strip->wrap();
|
||||||
|
animrig::ChannelBag &channelbag = dna_channelbag->wrap();
|
||||||
|
|
||||||
|
if (!key_strip.channelbag_remove(channelbag)) {
|
||||||
|
BKE_report(reports, RPT_ERROR, "This channelbag does not belong to this strip");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN, nullptr);
|
||||||
|
DEG_id_tag_update(action, ID_RECALC_ANIMATION);
|
||||||
|
}
|
||||||
|
|
||||||
static bool rna_KeyframeActionStrip_key_insert(ID *id,
|
static bool rna_KeyframeActionStrip_key_insert(ID *id,
|
||||||
KeyframeActionStrip *dna_strip,
|
KeyframeActionStrip *dna_strip,
|
||||||
Main *bmain,
|
Main *bmain,
|
||||||
@ -481,6 +520,34 @@ static bool rna_KeyframeActionStrip_key_insert(ID *id,
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<std::string> rna_ActionChannelBag_path(const PointerRNA *ptr)
|
||||||
|
{
|
||||||
|
animrig::Action &action = rna_action(ptr);
|
||||||
|
animrig::ChannelBag &cbag_to_find = rna_data_channelbag(ptr);
|
||||||
|
|
||||||
|
for (animrig::Layer *layer : action.layers()) {
|
||||||
|
for (int64_t strip_index : layer->strips().index_range()) {
|
||||||
|
const animrig::Strip *strip = layer->strip(strip_index);
|
||||||
|
if (!strip->is<animrig::KeyframeStrip>()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const animrig::KeyframeStrip &key_strip = strip->as<animrig::KeyframeStrip>();
|
||||||
|
const int64_t index = key_strip.find_channelbag_index(cbag_to_find);
|
||||||
|
if (index < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointerRNA layer_ptr = RNA_pointer_create(&action.id, &RNA_ActionLayer, layer);
|
||||||
|
const std::optional<std::string> layer_path = rna_ActionLayer_path(&layer_ptr);
|
||||||
|
BLI_assert_msg(layer_path, "Every animation layer should have a valid RNA path.");
|
||||||
|
return fmt::format("{}.strips[{}].channelbags[{}]", *layer_path, strip_index, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
static void rna_iterator_ChannelBag_fcurves_begin(CollectionPropertyIterator *iter,
|
static void rna_iterator_ChannelBag_fcurves_begin(CollectionPropertyIterator *iter,
|
||||||
PointerRNA *ptr)
|
PointerRNA *ptr)
|
||||||
{
|
{
|
||||||
@ -1433,6 +1500,9 @@ static void rna_def_keyframestrip_channelbags(BlenderRNA *brna, PropertyRNA *cpr
|
|||||||
{
|
{
|
||||||
StructRNA *srna;
|
StructRNA *srna;
|
||||||
|
|
||||||
|
FunctionRNA *func;
|
||||||
|
PropertyRNA *parm;
|
||||||
|
|
||||||
RNA_def_property_srna(cprop, "ActionChannelBags");
|
RNA_def_property_srna(cprop, "ActionChannelBags");
|
||||||
srna = RNA_def_struct(brna, "ActionChannelBags", nullptr);
|
srna = RNA_def_struct(brna, "ActionChannelBags", nullptr);
|
||||||
RNA_def_struct_sdna(srna, "KeyframeActionStrip");
|
RNA_def_struct_sdna(srna, "KeyframeActionStrip");
|
||||||
@ -1440,6 +1510,30 @@ static void rna_def_keyframestrip_channelbags(BlenderRNA *brna, PropertyRNA *cpr
|
|||||||
srna,
|
srna,
|
||||||
"Animation Channels for Slots",
|
"Animation Channels for Slots",
|
||||||
"For each action slot, a list of animation channels that are meant for that slot");
|
"For each action slot, a list of animation channels that are meant for that slot");
|
||||||
|
|
||||||
|
/* KeyframeStrip.channelbags.new(slot=...) */
|
||||||
|
func = RNA_def_function(srna, "new", "rna_ChannelBags_new");
|
||||||
|
RNA_def_function_ui_description(
|
||||||
|
func,
|
||||||
|
"Add a new channelbag to the strip, to contain animation channels for a specific slot");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||||
|
parm = RNA_def_pointer(func,
|
||||||
|
"slot",
|
||||||
|
"ActionSlot",
|
||||||
|
"Action Slot",
|
||||||
|
"The slot that should be animated by this channelbag");
|
||||||
|
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||||
|
|
||||||
|
/* Return value. */
|
||||||
|
parm = RNA_def_pointer(func, "channelbag", "ActionChannelBag", "", "Newly created channelbag");
|
||||||
|
RNA_def_function_return(func, parm);
|
||||||
|
|
||||||
|
/* KeyframeStrip.channelbags.remove(strip) */
|
||||||
|
func = RNA_def_function(srna, "remove", "rna_ChannelBags_remove");
|
||||||
|
RNA_def_function_flag(func, FUNC_USE_SELF_ID | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
|
||||||
|
RNA_def_function_ui_description(func, "Remove the channelbag from the strip");
|
||||||
|
parm = RNA_def_pointer(func, "channelbag", "ActionChannelBag", "", "The channelbag to remove");
|
||||||
|
RNA_def_parameter_flags(parm, PropertyFlag(0), PARM_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rna_def_action_keyframe_strip(BlenderRNA *brna)
|
static void rna_def_action_keyframe_strip(BlenderRNA *brna)
|
||||||
@ -1587,6 +1681,7 @@ static void rna_def_action_channelbag(BlenderRNA *brna)
|
|||||||
srna,
|
srna,
|
||||||
"Animation Channel Bag",
|
"Animation Channel Bag",
|
||||||
"Collection of animation channels, typically associated with an action slot");
|
"Collection of animation channels, typically associated with an action slot");
|
||||||
|
RNA_def_struct_path_func(srna, "rna_ActionChannelBag_path");
|
||||||
|
|
||||||
prop = RNA_def_property(srna, "slot_handle", PROP_INT, PROP_NONE);
|
prop = RNA_def_property(srna, "slot_handle", PROP_INT, PROP_NONE);
|
||||||
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
|
||||||
|
@ -164,6 +164,30 @@ class TestLegacyLayered(unittest.TestCase):
|
|||||||
self.assertSequenceEqual([], act.groups)
|
self.assertSequenceEqual([], act.groups)
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelBagsTest(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
anims = bpy.data.actions
|
||||||
|
while anims:
|
||||||
|
anims.remove(anims[0])
|
||||||
|
|
||||||
|
def test_create_remove_channelbag(self):
|
||||||
|
action = bpy.data.actions.new('TestAction')
|
||||||
|
|
||||||
|
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.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))
|
||||||
|
|
||||||
|
|
||||||
class DataPathTest(unittest.TestCase):
|
class DataPathTest(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
anims = bpy.data.actions
|
anims = bpy.data.actions
|
||||||
@ -173,12 +197,19 @@ class DataPathTest(unittest.TestCase):
|
|||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
action = bpy.data.actions.new('TestAction')
|
action = bpy.data.actions.new('TestAction')
|
||||||
|
|
||||||
|
slot = action.slots.new()
|
||||||
|
slot.name = 'OBTest'
|
||||||
|
self.assertEqual("bpy.data.actions['TestAction'].slots[\"OBTest\"]", repr(slot))
|
||||||
|
|
||||||
layer = action.layers.new(name="Layer")
|
layer = action.layers.new(name="Layer")
|
||||||
self.assertEqual("bpy.data.actions['TestAction'].layers[\"Layer\"]", repr(layer))
|
self.assertEqual("bpy.data.actions['TestAction'].layers[\"Layer\"]", repr(layer))
|
||||||
|
|
||||||
strip = layer.strips.new(type='KEYFRAME')
|
strip = layer.strips.new(type='KEYFRAME')
|
||||||
self.assertEqual("bpy.data.actions['TestAction'].layers[\"Layer\"].strips[0]", repr(strip))
|
self.assertEqual("bpy.data.actions['TestAction'].layers[\"Layer\"].strips[0]", repr(strip))
|
||||||
|
|
||||||
|
channelbag = strip.channelbags.new(slot)
|
||||||
|
self.assertEqual("bpy.data.actions['TestAction'].layers[\"Layer\"].strips[0].channelbags[0]", repr(channelbag))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global args
|
global args
|
||||||
|
Loading…
x
Reference in New Issue
Block a user