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:
@@ -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