From 29be81ec238c2e23e76e29d5cc2f7694409b8d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Thu, 4 Jan 2024 15:33:18 +0100 Subject: [PATCH] Anim: make RNA property `bone_collection.parent` writable Moving a bone collection to another parent is now possible in Python by assigning to `bone_collection.parent`. Thanks to Sergey for the implementation. --- .../blender/makesrna/intern/rna_armature.cc | 33 +++++++++++++++++-- tests/python/bl_animation_armature.py | 31 +++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/source/blender/makesrna/intern/rna_armature.cc b/source/blender/makesrna/intern/rna_armature.cc index aed5316ecaf..2e8d9df4c13 100644 --- a/source/blender/makesrna/intern/rna_armature.cc +++ b/source/blender/makesrna/intern/rna_armature.cc @@ -266,6 +266,34 @@ static PointerRNA rna_BoneCollection_parent_get(PointerRNA *ptr) return RNA_pointer_create(&arm->id, &RNA_BoneCollection, parent); } +static void rna_BoneCollection_parent_set(PointerRNA *ptr, + PointerRNA value, + struct ReportList *reports) +{ + using namespace blender::animrig; + + BoneCollection *self = (BoneCollection *)ptr->data; + BoneCollection *to_parent = (BoneCollection *)value.data; + + bArmature *armature = (bArmature *)ptr->owner_id; + + const int from_bcoll_index = armature_bonecoll_find_index(armature, self); + const int from_parent_index = armature_bonecoll_find_parent_index(armature, from_bcoll_index); + const int to_parent_index = armature_bonecoll_find_index(armature, to_parent); + + if (to_parent_index == from_bcoll_index || + armature_bonecoll_is_decendent_of(armature, from_bcoll_index, to_parent_index)) + { + BKE_report(reports, RPT_ERROR, "Cannot make a bone collection a decendent of itself"); + return; + } + + armature_bonecoll_move_to_parent( + armature, from_bcoll_index, -1, from_parent_index, to_parent_index); + + WM_main_add_notifier(NC_OBJECT | ND_BONE_COLLECTION, nullptr); +} + static int rna_BoneCollections_active_index_get(PointerRNA *ptr) { bArmature *arm = (bArmature *)ptr->data; @@ -2269,8 +2297,9 @@ static void rna_def_bonecollection(BlenderRNA *brna) prop = RNA_def_property(srna, "parent", PROP_POINTER, PROP_NONE); RNA_def_property_struct_type(prop, "BoneCollection"); - RNA_def_property_pointer_funcs(prop, "rna_BoneCollection_parent_get", nullptr, nullptr, nullptr); - RNA_def_property_clear_flag(prop, PROP_EDITABLE); + RNA_def_property_flag(prop, PROP_EDITABLE); + RNA_def_property_pointer_funcs( + prop, "rna_BoneCollection_parent_get", "rna_BoneCollection_parent_set", nullptr, nullptr); RNA_def_property_ui_text(prop, "Parent", "Parent bone collection. Note that accessing this requires a scan of " diff --git a/tests/python/bl_animation_armature.py b/tests/python/bl_animation_armature.py index a83e5caa4c6..7bc3efdc0fe 100644 --- a/tests/python/bl_animation_armature.py +++ b/tests/python/bl_animation_armature.py @@ -111,6 +111,37 @@ class BoneCollectionTest(unittest.TestCase): # Check the array order. self.assertEqual([root1, r1_child1, root2, r1_child1_001, r2_child1, r2_child2], list(bcolls.all)) + def test_parent_property(self): + # Just to keep the rest of the code shorter. + bcolls = self.arm.collections + + self.assertEqual([], list(bcolls), "By default an Armature should have no collections") + + # Build a hierarchy. + root1 = bcolls.new('root1') + r1_child1 = bcolls.new('r1_child1', parent=root1) + r1_child2 = bcolls.new('r1_child2', parent=root1) + root2 = bcolls.new('root2') + r2_child1 = bcolls.new('r2_child1', parent=root2) + r2_child2 = bcolls.new('r2_child2', parent=root2) + + # Check getting the parent. + self.assertEqual(root1, r1_child1.parent) + self.assertEqual(root1, r1_child2.parent) + self.assertEqual(root2, r2_child1.parent) + self.assertEqual(root2, r2_child2.parent) + + # Move r1_child1 to be a child of root2 by assigning the parent. + r1_child1.parent = root2 + self.assertEqual(root2, r1_child1.parent) + + # Check the sibling order. + self.assertEqual([r2_child1, r2_child2, r1_child1], list(root2.children)) + + # Make r1_child1 a root. + r1_child1.parent = None + self.assertIsNone(r1_child1.parent) + def test_bone_collection_bones(self): # Build a hierarchy on the armature. bcolls = self.arm.collections