From e7191f8390b275b54826ea4a1e06e57e5596b289 Mon Sep 17 00:00:00 2001 From: Alberto-Luaces Date: Mon, 16 Jun 2025 15:59:48 +0200 Subject: [PATCH 1/3] Python API: update animation example in quickstart docs for slots Update the Animation example in the Python API: Quickstart documentation to use slotted Actions. Pull Request: https://projects.blender.org/blender/blender/pulls/140334 --- doc/python_api/rst/info_quickstart.rst | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/doc/python_api/rst/info_quickstart.rst b/doc/python_api/rst/info_quickstart.rst index 4e7382b9dd6..d422bd9b656 100644 --- a/doc/python_api/rst/info_quickstart.rst +++ b/doc/python_api/rst/info_quickstart.rst @@ -447,9 +447,22 @@ Using low-level functions: .. code-block:: python obj = bpy.context.object - obj.animation_data_create() - obj.animation_data.action = bpy.data.actions.new(name="MyAction") - fcu_z = obj.animation_data.action.fcurves.new(data_path="location", index=2) + + # Create the action, with a slot for the object, a layer, and a keyframe strip: + action = bpy.data.actions.new(name="MyAction") + slot = action.slots.new(obj.id_type, obj.name) + strip = action.layers.new("MyLayer").strips.new(type='KEYFRAME') + + # Create a channelbag to hold the F-Curves for the slot: + channelbag = strip.channelbag(slot, ensure=True) + + # Create the F-Curve with two keyframes: + fcu_z = channelbag.fcurves.new(data_path="location", index=2) fcu_z.keyframe_points.add(2) fcu_z.keyframe_points[0].co = 10.0, 0.0 fcu_z.keyframe_points[1].co = 20.0, 1.0 + + # Assign the action and the slot to the object: + adt = obj.animation_data_create() + adt.action = action + adt.action_slot = slot From c605c71a96db0bb18bb4f42f545e74a84e95f3b6 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 16 Jun 2025 15:52:18 +0200 Subject: [PATCH 2/3] Fix #140451: Revert "Fix #140396: bmesh.ops.dissolve_edges fails to dissolve vertices" The BMesh python API was fully broken by this commit. While the fix seems to be reasonably simple, it is safer for now to revert the faulty commit, as the breakage is fairly impactful for people using 4.5 and/or 5.0 daily builds. This reverts commit bd3a66a4167284aa2f215147f4128fcc26e307dc. --- .../bmesh/intern/bmesh_operator_api.hh | 18 +-------- .../blender/bmesh/intern/bmesh_operators.cc | 39 ++----------------- .../blender/bmesh/operators/bmo_dissolve.cc | 4 +- 3 files changed, 6 insertions(+), 55 deletions(-) diff --git a/source/blender/bmesh/intern/bmesh_operator_api.hh b/source/blender/bmesh/intern/bmesh_operator_api.hh index f36c924e404..983cc3e3cfe 100644 --- a/source/blender/bmesh/intern/bmesh_operator_api.hh +++ b/source/blender/bmesh/intern/bmesh_operator_api.hh @@ -12,7 +12,6 @@ #include "BLI_utildefines.h" #include -#include #include "bmesh_class.hh" @@ -240,15 +239,6 @@ union eBMOpSlotSubType_Union { eBMOpSlotSubType_Int intg; }; -enum eBMOpSlotFlag : uint8_t { - /** - * This flag is set when the operators value has been set. - * Use this so it's possible to have non-zero defaults for properties. - */ - BMO_OP_SLOT_FLAG_IS_SET = (1 << 0), -}; -ENUM_OPERATORS(eBMOpSlotFlag, BMO_OP_SLOT_FLAG_IS_SET) - struct BMO_FlagSet { int value; const char *identifier; @@ -262,8 +252,8 @@ struct BMOpSlot { eBMOpSlotType slot_type; eBMOpSlotSubType_Union slot_subtype; - eBMOpSlotFlag flag; int len; + // int flag; /* UNUSED */ // int index; /* index within slot array */ /* UNUSED */ union { int i; @@ -559,16 +549,10 @@ void BMO_op_flag_disable(BMesh *bm, BMOperator *op, int op_flag); void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float f); float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name); -std::optional BMO_slot_float_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name); void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int i); int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name); -std::optional BMO_slot_int_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name); void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, bool i); bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name); -std::optional BMO_slot_bool_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name); /** * Return a copy of the element buffer. */ diff --git a/source/blender/bmesh/intern/bmesh_operators.cc b/source/blender/bmesh/intern/bmesh_operators.cc index d93dc25ac26..d56a9b80f5a 100644 --- a/source/blender/bmesh/intern/bmesh_operators.cc +++ b/source/blender/bmesh/intern/bmesh_operators.cc @@ -295,8 +295,6 @@ void _bmo_slot_copy(BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS], else { slot_dst->data = slot_src->data; } - - slot_dst->flag = slot_src->flag; } /* @@ -314,7 +312,6 @@ void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_n } slot->data.f = f; - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, const int i) @@ -326,7 +323,6 @@ void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_nam } slot->data.i = i; - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, const bool i) @@ -338,7 +334,6 @@ void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_na } slot->data.i = i; - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } void BMO_slot_mat_set(BMOperator *op, @@ -367,7 +362,6 @@ void BMO_slot_mat_set(BMOperator *op, zero_m4(static_cast(slot->data.p)); } - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } void BMO_slot_mat4_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], @@ -415,7 +409,6 @@ void BMO_slot_ptr_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_nam } slot->data.p = p; - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } void BMO_slot_vec_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], @@ -429,51 +422,31 @@ void BMO_slot_vec_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], } copy_v3_v3(slot->data.vec, vec); - slot->flag |= BMO_OP_SLOT_FLAG_IS_SET; } float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name) -{ - return BMO_slot_float_get_optional(slot_args, slot_name).value_or(0.0f); -} - -std::optional BMO_slot_float_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name) { BMOpSlot *slot = BMO_slot_get(slot_args, slot_name); BLI_assert(slot->slot_type == BMO_OP_SLOT_FLT); if (!(slot->slot_type == BMO_OP_SLOT_FLT)) { - return std::nullopt; - } - if ((slot->flag & BMO_OP_SLOT_FLAG_IS_SET) == 0) { - return std::nullopt; + return 0.0f; } return slot->data.f; } -std::optional BMO_slot_int_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name) +int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name) { BMOpSlot *slot = BMO_slot_get(slot_args, slot_name); BLI_assert(slot->slot_type == BMO_OP_SLOT_INT); if (!(slot->slot_type == BMO_OP_SLOT_INT)) { - return std::nullopt; - } - if ((slot->flag & BMO_OP_SLOT_FLAG_IS_SET) == 0) { - return std::nullopt; + return 0; } return slot->data.i; } -int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name) -{ - return BMO_slot_int_get_optional(slot_args, slot_name).value_or(0); -} - -std::optional BMO_slot_bool_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], - const char *slot_name) +bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name) { BMOpSlot *slot = BMO_slot_get(slot_args, slot_name); BLI_assert(slot->slot_type == BMO_OP_SLOT_BOOL); @@ -483,10 +456,6 @@ std::optional BMO_slot_bool_get_optional(BMOpSlot slot_args[BMO_OP_MAX_SLO return slot->data.i; } -bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name) -{ - return BMO_slot_bool_get_optional(slot_args, slot_name).value_or(false); -} void *BMO_slot_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int *len) { diff --git a/source/blender/bmesh/operators/bmo_dissolve.cc b/source/blender/bmesh/operators/bmo_dissolve.cc index b0ea5877f5a..f377a0dd269 100644 --- a/source/blender/bmesh/operators/bmo_dissolve.cc +++ b/source/blender/bmesh/operators/bmo_dissolve.cc @@ -387,9 +387,7 @@ void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op) * This lets the test ignore that tiny bit of math error so users won't notice. */ const float angle_epsilon = RAD2DEGF(0.0001f); - /* When unset, don't limit dissolving vertices at all. */ - const float angle_threshold = - BMO_slot_float_get_optional(op->slots_in, "angle_threshold").value_or(M_PI); + const float angle_threshold = BMO_slot_float_get(op->slots_in, "angle_threshold"); /* Use verts when told to... except, do *not* use verts when angle_threshold is 0.0. */ const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts") && From b6cccca6617c795df4eef6a06f3f73dfdc962b15 Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Mon, 16 Jun 2025 16:17:06 +0200 Subject: [PATCH 3/3] BMesh: Python: Add minimal API test for bmesh. Hopefully will avoid critical failures like #140451 in the future. --- tests/python/CMakeLists.txt | 5 +++++ tests/python/bl_pyapi_bmesh.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 tests/python/bl_pyapi_bmesh.py diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index 0375fbdec3b..78cd38d7a20 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -261,6 +261,11 @@ add_blender_test( --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_text.py ) +add_blender_test( + script_pyapi_bmesh + --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bmesh.py +) + add_blender_test( script_pyapi_grease_pencil --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_grease_pencil.py diff --git a/tests/python/bl_pyapi_bmesh.py b/tests/python/bl_pyapi_bmesh.py new file mode 100644 index 00000000000..3a7d4e4c5fe --- /dev/null +++ b/tests/python/bl_pyapi_bmesh.py @@ -0,0 +1,31 @@ +# SPDX-FileCopyrightText: 2025 Blender Authors +# +# SPDX-License-Identifier: Apache-2.0 + +# ./blender.bin --background --python tests/python/bl_pyapi_bmesh.py -- --verbose +import bmesh +import unittest + + +class TestBMeshBasic(unittest.TestCase): + + def test_create_uvsphere(self): + bm = bmesh.new() + bmesh.ops.create_uvsphere( + bm, + u_segments=8, + v_segments=5, + radius=1.0, + ) + + self.assertEqual(len(bm.verts), 34) + self.assertEqual(len(bm.edges), 72) + self.assertEqual(len(bm.faces), 40) + + bm.free() + + +if __name__ == "__main__": + import sys + sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []) + unittest.main()