Merge branch 'blender-v4.4-release'
[blender.git] / source / blender / python / intern / bpy_rna_anim.cc
blobf7b71a199d1ae47024389371d7de838ff57ec6cb
1 /* SPDX-FileCopyrightText: 2023 Blender Authors
3 * SPDX-License-Identifier: GPL-2.0-or-later */
5 /** \file
6 * \ingroup pythonintern
8 * This file defines the animation related methods used in `bpy_rna.cc`.
9 */
11 #include <Python.h>
12 #include <cfloat> /* FLT_MAX */
14 #include "MEM_guardedalloc.h"
16 #include "BLI_string.h"
17 #include "BLI_string_utils.hh"
19 #include "DNA_anim_types.h"
20 #include "DNA_scene_types.h"
22 #include "ED_keyframing.hh"
24 #include "ANIM_keyframing.hh"
26 #include "BKE_anim_data.hh"
27 #include "BKE_animsys.h"
28 #include "BKE_context.hh"
29 #include "BKE_fcurve.hh"
30 #include "BKE_global.hh"
31 #include "BKE_idtype.hh"
32 #include "BKE_lib_id.hh"
33 #include "BKE_report.hh"
35 #include "RNA_access.hh"
36 #include "RNA_enum_types.hh"
37 #include "RNA_path.hh"
38 #include "RNA_prototypes.hh"
40 #include "WM_api.hh"
41 #include "WM_types.hh"
43 #include "bpy_capi_utils.hh"
44 #include "bpy_rna.hh"
45 #include "bpy_rna_anim.hh"
47 #include "../generic/py_capi_rna.hh"
48 #include "../generic/python_utildefines.hh"
50 #include "DEG_depsgraph.hh"
51 #include "DEG_depsgraph_build.hh"
53 /* for keyframes and drivers */
54 static int pyrna_struct_anim_args_parse_ex(PointerRNA *ptr,
55 const char *error_prefix,
56 const char *path,
57 const char **r_path_full,
58 int *r_index,
59 bool *r_path_no_validate)
61 const bool is_idbase = RNA_struct_is_ID(ptr->type);
62 PropertyRNA *prop;
63 PointerRNA r_ptr;
65 if (ptr->data == nullptr) {
66 PyErr_Format(
67 PyExc_TypeError, "%.200s this struct has no data, can't be animated", error_prefix);
68 return -1;
71 /* full paths can only be given from ID base */
72 if (is_idbase) {
73 int path_index = -1;
74 if (RNA_path_resolve_property_full(ptr, path, &r_ptr, &prop, &path_index) == false) {
75 prop = nullptr;
77 else if (path_index != -1) {
78 PyErr_Format(PyExc_ValueError,
79 "%.200s path includes index, must be a separate argument",
80 error_prefix,
81 path);
82 return -1;
84 else if (ptr->owner_id != r_ptr.owner_id) {
85 PyErr_Format(PyExc_ValueError, "%.200s path spans ID blocks", error_prefix, path);
86 return -1;
89 else {
90 prop = RNA_struct_find_property(ptr, path);
91 r_ptr = *ptr;
94 if (prop == nullptr) {
95 if (r_path_no_validate) {
96 *r_path_no_validate = true;
97 return -1;
99 PyErr_Format(PyExc_TypeError, "%.200s property \"%s\" not found", error_prefix, path);
100 return -1;
103 if (r_path_no_validate) {
104 /* Don't touch the index. */
106 else {
107 if (!RNA_property_animateable(&r_ptr, prop)) {
108 PyErr_Format(PyExc_TypeError, "%.200s property \"%s\" not animatable", error_prefix, path);
109 return -1;
112 if (RNA_property_array_check(prop) == 0) {
113 if ((*r_index) == -1) {
114 *r_index = 0;
116 else {
117 PyErr_Format(PyExc_TypeError,
118 "%.200s index %d was given while property \"%s\" is not an array",
119 error_prefix,
120 *r_index,
121 path);
122 return -1;
125 else {
126 const int array_len = RNA_property_array_length(&r_ptr, prop);
127 if ((*r_index) < -1 || (*r_index) >= array_len) {
128 PyErr_Format(PyExc_TypeError,
129 "%.200s index out of range \"%s\", given %d, array length is %d",
130 error_prefix,
131 path,
132 *r_index,
133 array_len);
134 return -1;
139 if (is_idbase) {
140 *r_path_full = BLI_strdup(path);
142 else {
143 const std::optional<std::string> path_full = RNA_path_from_ID_to_property(&r_ptr, prop);
144 *r_path_full = path_full ? BLI_strdup(path_full->c_str()) : nullptr;
146 if (*r_path_full == nullptr) {
147 PyErr_Format(PyExc_TypeError, "%.200s could not make path to \"%s\"", error_prefix, path);
148 return -1;
152 return 0;
155 static int pyrna_struct_anim_args_parse(PointerRNA *ptr,
156 const char *error_prefix,
157 const char *path,
158 const char **r_path_full,
159 int *r_index)
161 return pyrna_struct_anim_args_parse_ex(ptr, error_prefix, path, r_path_full, r_index, nullptr);
165 * Unlike #pyrna_struct_anim_args_parse \a r_path_full may be copied from \a path.
167 static int pyrna_struct_anim_args_parse_no_resolve(PointerRNA *ptr,
168 const char *error_prefix,
169 const char *path,
170 const char **r_path_full)
172 const bool is_idbase = RNA_struct_is_ID(ptr->type);
173 if (is_idbase) {
174 *r_path_full = path;
175 return 0;
178 const std::optional<std::string> path_prefix = RNA_path_from_ID_to_struct(ptr);
179 if (!path_prefix) {
180 PyErr_Format(PyExc_TypeError,
181 "%.200s could not make path for type %s",
182 error_prefix,
183 RNA_struct_identifier(ptr->type));
184 return -1;
187 if (*path == '[') {
188 *r_path_full = BLI_string_joinN(path_prefix->c_str(), path);
190 else {
191 *r_path_full = BLI_string_join_by_sep_charN('.', path_prefix->c_str(), path);
194 return 0;
197 static int pyrna_struct_anim_args_parse_no_resolve_fallback(PointerRNA *ptr,
198 const char *error_prefix,
199 const char *path,
200 const char **r_path_full,
201 int *r_index)
203 bool path_unresolved = false;
204 if (pyrna_struct_anim_args_parse_ex(
205 ptr, error_prefix, path, r_path_full, r_index, &path_unresolved) == -1)
207 if (path_unresolved == true) {
208 if (pyrna_struct_anim_args_parse_no_resolve(ptr, error_prefix, path, r_path_full) == -1) {
209 return -1;
212 else {
213 return -1;
216 return 0;
219 /* internal use for insert and delete */
220 static int pyrna_struct_keyframe_parse(PointerRNA *ptr,
221 PyObject *args,
222 PyObject *kw,
223 const char *parse_str,
224 const char *error_prefix,
225 /* return values */
226 const char **r_path_full,
227 int *r_index,
228 float *r_cfra,
229 const char **r_group_name,
230 int *r_options,
231 eBezTriple_KeyframeType *r_keytype)
233 static const char *kwlist[] = {
234 "data_path", "index", "frame", "group", "options", "keytype", nullptr};
235 PyObject *pyoptions = nullptr;
236 char *keytype_name = nullptr;
237 const char *path;
239 /* NOTE: `parse_str` MUST start with `s|ifsO!`. */
240 if (!PyArg_ParseTupleAndKeywords(args,
242 parse_str,
243 (char **)kwlist,
244 &path,
245 r_index,
246 r_cfra,
247 r_group_name,
248 &PySet_Type,
249 &pyoptions,
250 &keytype_name))
252 return -1;
255 if (pyrna_struct_anim_args_parse(ptr, error_prefix, path, r_path_full, r_index) == -1) {
256 return -1;
259 if (*r_cfra == FLT_MAX) {
260 *r_cfra = CTX_data_scene(BPY_context_get())->r.cfra;
263 /* flag may be null (no option currently for remove keyframes e.g.). */
264 if (r_options) {
265 if (pyoptions &&
266 (pyrna_enum_bitfield_from_set(
267 rna_enum_keying_flag_api_items, pyoptions, r_options, error_prefix) == -1))
269 return -1;
272 *r_options |= INSERTKEY_NO_USERPREF;
275 if (r_keytype) {
276 int keytype_as_int = 0;
277 if (keytype_name && pyrna_enum_value_from_id(rna_enum_beztriple_keyframe_type_items,
278 keytype_name,
279 &keytype_as_int,
280 error_prefix) == -1)
282 return -1;
284 *r_keytype = eBezTriple_KeyframeType(keytype_as_int);
287 return 0; /* success */
290 char pyrna_struct_keyframe_insert_doc[] =
291 ".. method:: keyframe_insert(data_path, index=-1, frame=bpy.context.scene.frame_current, "
292 "group=\"\", options=set(), keytype='KEYFRAME')\n"
293 "\n"
294 " Insert a keyframe on the property given, adding fcurves and animation data when "
295 "necessary.\n"
296 "\n"
297 " :arg data_path: path to the property to key, analogous to the fcurve's data path.\n"
298 " :type data_path: str\n"
299 " :arg index: array index of the property to key.\n"
300 " Defaults to -1 which will key all indices or a single channel if the property is not "
301 "an array.\n"
302 " :type index: int\n"
303 " :arg frame: The frame on which the keyframe is inserted, defaulting to the current "
304 "frame.\n"
305 " :type frame: float\n"
306 " :arg group: The name of the group the F-Curve should be added to if it doesn't exist "
307 "yet.\n"
308 " :type group: str\n"
309 " :arg options: Optional set of flags:\n"
310 "\n"
311 " - ``INSERTKEY_NEEDED`` Only insert keyframes where they're needed in the relevant "
312 "F-Curves.\n"
313 " - ``INSERTKEY_VISUAL`` Insert keyframes based on 'visual transforms'.\n"
314 " - ``INSERTKEY_XYZ_TO_RGB`` This flag is no longer in use, and is here so that code "
315 "that uses it doesn't break. The XYZ=RGB coloring is determined by the animation "
316 "preferences.\n"
317 " - ``INSERTKEY_REPLACE`` Only replace already existing keyframes.\n"
318 " - ``INSERTKEY_AVAILABLE`` Only insert into already existing F-Curves.\n"
319 " - ``INSERTKEY_CYCLE_AWARE`` Take cyclic extrapolation into account "
320 "(Cycle-Aware Keying option).\n"
321 " :type options: set[str]\n"
322 " :arg keytype: Type of the key: 'KEYFRAME', 'BREAKDOWN', 'MOVING_HOLD', 'EXTREME', "
323 "'JITTER', or 'GENERATED'\n"
324 " :type keytype: str\n"
325 " :return: Success of keyframe insertion.\n"
326 " :rtype: bool\n";
327 PyObject *pyrna_struct_keyframe_insert(BPy_StructRNA *self, PyObject *args, PyObject *kw)
329 using namespace blender::animrig;
330 /* args, pyrna_struct_keyframe_parse handles these */
331 const char *path_full = nullptr;
332 int index = -1;
333 float cfra = FLT_MAX;
334 const char *group_name = nullptr;
335 eBezTriple_KeyframeType keytype = BEZT_KEYTYPE_KEYFRAME;
336 int options = 0;
338 PYRNA_STRUCT_CHECK_OBJ(self);
340 if (pyrna_struct_keyframe_parse(&self->ptr.value(),
341 args,
343 "s|$ifsO!s:bpy_struct.keyframe_insert()",
344 "bpy_struct.keyframe_insert()",
345 &path_full,
346 &index,
347 &cfra,
348 &group_name,
349 &options,
350 &keytype) == -1)
352 return nullptr;
355 ReportList reports;
356 bool result = false;
358 BKE_reports_init(&reports, RPT_STORE);
360 /* This assumes that keyframes are only added on original data & using the active depsgraph. If
361 * it turns out to be necessary for some reason to insert keyframes on evaluated objects, we can
362 * revisit this and add an explicit `depsgraph` keyword argument to the function call.
364 * The depsgraph is only used for evaluating the NLA so this might not be needed in the future.
366 bContext *C = BPY_context_get();
367 Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
368 const AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph,
369 cfra);
371 if (self->ptr->type == &RNA_NlaStrip) {
372 /* Handle special properties for NLA Strips, whose F-Curves are stored on the
373 * strips themselves. These are stored separately or else the properties will
374 * not have any effect.
377 PointerRNA &ptr = *self->ptr;
378 PropertyRNA *prop = nullptr;
379 const char *prop_name;
381 /* Retrieve the property identifier from the full path, since we can't get it any other way */
382 prop_name = strrchr(path_full, '.');
383 if ((prop_name >= path_full) && (prop_name + 1 < path_full + strlen(path_full))) {
384 prop = RNA_struct_find_property(&ptr, prop_name + 1);
387 if (prop) {
388 NlaStrip *strip = static_cast<NlaStrip *>(ptr.data);
389 FCurve *fcu = BKE_fcurve_find(&strip->fcurves, RNA_property_identifier(prop), index);
390 result = insert_keyframe_direct(&reports,
391 ptr,
392 prop,
393 fcu,
394 &anim_eval_context,
395 eBezTriple_KeyframeType(keytype),
396 nullptr,
397 eInsertKeyFlags(options));
399 else {
400 BKE_reportf(&reports, RPT_ERROR, "Could not resolve path (%s)", path_full);
403 else {
404 BLI_assert(BKE_id_is_in_global_main(self->ptr->owner_id));
406 const std::optional<blender::StringRefNull> channel_group = group_name ?
407 std::optional(group_name) :
408 std::nullopt;
409 PointerRNA id_pointer = RNA_id_pointer_create(self->ptr->owner_id);
410 CombinedKeyingResult combined_result = insert_keyframes(G_MAIN,
411 &id_pointer,
412 channel_group,
413 {{path_full, {}, index}},
414 std::nullopt,
415 anim_eval_context,
416 eBezTriple_KeyframeType(keytype),
417 eInsertKeyFlags(options));
418 const int success_count = combined_result.get_count(SingleKeyingResult::SUCCESS);
419 if (success_count == 0) {
420 /* Ideally this would use the GUI presentation of RPT_ERROR, as the resulting pop-up has more
421 * vertical space than the single-line warning in the status bar. However, semantically these
422 * may not be errors at all, as skipping the keying of certain properties due to the 'only
423 * insert available' flag is not an error.
425 * Furthermore, using RPT_ERROR here would cause this function to raise a Python exception,
426 * rather than returning a boolean. */
427 combined_result.generate_reports(&reports, RPT_WARNING);
429 result = success_count != 0;
432 MEM_freeN((void *)path_full);
434 if (BPy_reports_to_error(&reports, PyExc_RuntimeError, false) == -1) {
435 BKE_reports_free(&reports);
436 return nullptr;
438 BKE_report_print_level_set(&reports, G.quiet ? RPT_WARNING : RPT_DEBUG);
439 BPy_reports_write_stdout(&reports, nullptr);
440 BKE_reports_free(&reports);
442 if (result) {
443 WM_event_add_notifier(C, NC_ANIMATION | ND_ANIMCHAN | NA_EDITED, nullptr);
446 return PyBool_FromLong(result);
449 char pyrna_struct_keyframe_delete_doc[] =
450 ".. method:: keyframe_delete(data_path, index=-1, frame=bpy.context.scene.frame_current, "
451 "group=\"\")\n"
452 "\n"
453 " Remove a keyframe from this properties fcurve.\n"
454 "\n"
455 " :arg data_path: path to the property to remove a key, analogous to the fcurve's data "
456 "path.\n"
457 " :type data_path: str\n"
458 " :arg index: array index of the property to remove a key. Defaults to -1 removing all "
459 "indices or a single channel if the property is not an array.\n"
460 " :type index: int\n"
461 " :arg frame: The frame on which the keyframe is deleted, defaulting to the current frame.\n"
462 " :type frame: float\n"
463 " :arg group: The name of the group the F-Curve should be added to if it doesn't exist "
464 "yet.\n"
465 " :type group: str\n"
466 " :return: Success of keyframe deletion.\n"
467 " :rtype: bool\n";
468 PyObject *pyrna_struct_keyframe_delete(BPy_StructRNA *self, PyObject *args, PyObject *kw)
470 /* args, pyrna_struct_keyframe_parse handles these */
471 const char *path_full = nullptr;
472 int index = -1;
473 float cfra = FLT_MAX;
474 const char *group_name = nullptr;
476 PYRNA_STRUCT_CHECK_OBJ(self);
478 if (pyrna_struct_keyframe_parse(&self->ptr.value(),
479 args,
481 "s|$ifsOs!:bpy_struct.keyframe_delete()",
482 "bpy_struct.keyframe_insert()",
483 &path_full,
484 &index,
485 &cfra,
486 &group_name,
487 nullptr,
488 nullptr) == -1)
490 return nullptr;
493 ReportList reports;
494 bool result = false;
496 BKE_reports_init(&reports, RPT_STORE);
498 if (self->ptr->type == &RNA_NlaStrip) {
499 /* Handle special properties for NLA Strips, whose F-Curves are stored on the
500 * strips themselves. These are stored separately or else the properties will
501 * not have any effect.
504 PointerRNA ptr = *self->ptr;
505 PropertyRNA *prop = nullptr;
506 const char *prop_name;
508 /* Retrieve the property identifier from the full path, since we can't get it any other way */
509 prop_name = strrchr(path_full, '.');
510 if ((prop_name >= path_full) && (prop_name + 1 < path_full + strlen(path_full))) {
511 prop = RNA_struct_find_property(&ptr, prop_name + 1);
514 if (prop) {
515 ID *id = ptr.owner_id;
516 NlaStrip *strip = static_cast<NlaStrip *>(ptr.data);
517 FCurve *fcu = BKE_fcurve_find(&strip->fcurves, RNA_property_identifier(prop), index);
519 /* NOTE: This should be true, or else we wouldn't be able to get here. */
520 BLI_assert(fcu != nullptr);
522 if (BKE_fcurve_is_protected(fcu)) {
523 BKE_reportf(
524 &reports,
525 RPT_WARNING,
526 "Not deleting keyframe for locked F-Curve for NLA Strip influence on %s - %s '%s'",
527 strip->name,
528 BKE_idtype_idcode_to_name(GS(id->name)),
529 id->name + 2);
531 else {
532 /* remove the keyframe directly
533 * NOTE: cannot use delete_keyframe_fcurve(), as that will free the curve,
534 * and delete_keyframe() expects the FCurve to be part of an action
536 bool found = false;
537 int i;
539 /* try to find index of beztriple to get rid of */
540 i = BKE_fcurve_bezt_binarysearch_index(fcu->bezt, cfra, fcu->totvert, &found);
541 if (found) {
542 /* delete the key at the index (will sanity check + do recalc afterwards) */
543 BKE_fcurve_delete_key(fcu, i);
544 BKE_fcurve_handles_recalc(fcu);
545 result = true;
549 else {
550 BKE_reportf(&reports, RPT_ERROR, "Could not resolve path (%s)", path_full);
553 else {
554 RNAPath rna_path = {path_full, std::nullopt, index};
555 if (index < 0) {
556 rna_path.index = std::nullopt;
558 result = (blender::animrig::delete_keyframe(
559 G.main, &reports, self->ptr->owner_id, rna_path, cfra) != 0);
562 MEM_freeN((void *)path_full);
564 if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) {
565 return nullptr;
568 return PyBool_FromLong(result);
571 char pyrna_struct_driver_add_doc[] =
572 ".. method:: driver_add(path, index=-1)\n"
573 "\n"
574 " Adds driver(s) to the given property\n"
575 "\n"
576 " :arg path: path to the property to drive, analogous to the fcurve's data path.\n"
577 " :type path: str\n"
578 " :arg index: array index of the property drive. Defaults to -1 for all indices or a single "
579 "channel if the property is not an array.\n"
580 " :type index: int\n"
581 " :return: The driver added or a list of drivers when index is -1.\n"
582 " :rtype: :class:`bpy.types.FCurve` | list[:class:`bpy.types.FCurve`]\n";
583 PyObject *pyrna_struct_driver_add(BPy_StructRNA *self, PyObject *args)
585 const char *path, *path_full;
586 int index = -1;
588 PYRNA_STRUCT_CHECK_OBJ(self);
590 if (!PyArg_ParseTuple(args, "s|i:driver_add", &path, &index)) {
591 return nullptr;
594 if (pyrna_struct_anim_args_parse(
595 &self->ptr.value(), "bpy_struct.driver_add():", path, &path_full, &index) == -1)
597 return nullptr;
600 PyObject *ret = nullptr;
601 ReportList reports;
602 int result;
604 BKE_reports_init(&reports, RPT_STORE);
606 result = ANIM_add_driver(&reports,
607 self->ptr->owner_id,
608 path_full,
609 index,
610 CREATEDRIVER_WITH_FMODIFIER,
611 DRIVER_TYPE_PYTHON);
613 if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) {
614 return nullptr;
617 if (result) {
618 ID *id = self->ptr->owner_id;
619 AnimData *adt = BKE_animdata_from_id(id);
620 FCurve *fcu;
622 PointerRNA tptr;
624 if (index == -1) { /* all, use a list */
625 int i = 0;
626 ret = PyList_New(0);
627 while ((fcu = BKE_fcurve_find(&adt->drivers, path_full, i++))) {
628 tptr = RNA_pointer_create_discrete(id, &RNA_FCurve, fcu);
629 PyList_APPEND(ret, pyrna_struct_CreatePyObject(&tptr));
632 else {
633 fcu = BKE_fcurve_find(&adt->drivers, path_full, index);
634 tptr = RNA_pointer_create_discrete(id, &RNA_FCurve, fcu);
635 ret = pyrna_struct_CreatePyObject(&tptr);
638 bContext *context = BPY_context_get();
639 WM_event_add_notifier(BPY_context_get(), NC_ANIMATION | ND_FCURVES_ORDER, nullptr);
640 DEG_id_tag_update(id, ID_RECALC_SYNC_TO_EVAL);
641 DEG_relations_tag_update(CTX_data_main(context));
643 else {
644 /* XXX: should be handled by reports. */
645 PyErr_SetString(PyExc_TypeError,
646 "bpy_struct.driver_add(): failed because of an internal error");
647 return nullptr;
650 MEM_freeN((void *)path_full);
652 return ret;
655 char pyrna_struct_driver_remove_doc[] =
656 ".. method:: driver_remove(path, index=-1)\n"
657 "\n"
658 " Remove driver(s) from the given property\n"
659 "\n"
660 " :arg path: path to the property to drive, analogous to the fcurve's data path.\n"
661 " :type path: str\n"
662 " :arg index: array index of the property drive. Defaults to -1 for all indices or a single "
663 "channel if the property is not an array.\n"
664 " :type index: int\n"
665 " :return: Success of driver removal.\n"
666 " :rtype: bool\n";
667 PyObject *pyrna_struct_driver_remove(BPy_StructRNA *self, PyObject *args)
669 const char *path, *path_full;
670 int index = -1;
672 PYRNA_STRUCT_CHECK_OBJ(self);
674 if (!PyArg_ParseTuple(args, "s|i:driver_remove", &path, &index)) {
675 return nullptr;
678 if (pyrna_struct_anim_args_parse_no_resolve_fallback(
679 &self->ptr.value(), "bpy_struct.driver_remove():", path, &path_full, &index) == -1)
681 return nullptr;
684 short result;
685 ReportList reports;
687 BKE_reports_init(&reports, RPT_STORE);
689 result = ANIM_remove_driver(self->ptr->owner_id, path_full, index);
691 if (path != path_full) {
692 MEM_freeN((void *)path_full);
695 if (BPy_reports_to_error(&reports, PyExc_RuntimeError, true) == -1) {
696 return nullptr;
699 bContext *context = BPY_context_get();
700 WM_event_add_notifier(context, NC_ANIMATION | ND_FCURVES_ORDER, nullptr);
701 DEG_relations_tag_update(CTX_data_main(context));
703 return PyBool_FromLong(result);