diff --git a/source/blender/blenkernel/BKE_animsys.h b/source/blender/blenkernel/BKE_animsys.h index 0b25f4ea501..77191041511 100644 --- a/source/blender/blenkernel/BKE_animsys.h +++ b/source/blender/blenkernel/BKE_animsys.h @@ -183,6 +183,25 @@ void BKE_animdata_fix_paths_rename_all(struct ID *ref_id, */ bool BKE_animdata_fix_paths_remove(struct ID *id, const char *prefix); +/** + * Remove drivers that have an RNA path starting with `prefix`. + * + * \return true if any driver was removed. + */ +bool BKE_animdata_driver_path_remove(struct ID *id, const char *prefix); + +/** + * Remove all drivers from the given struct. + * + * \param type needs to be a struct owned by the given ID. + * \param data the actual struct data, needs to be the data for the StructRNA. + * + * \return true if any driver was removed. + */ +bool BKE_animdata_drivers_remove_for_rna_struct(struct ID &owner_id, + struct StructRNA &type, + void *data); + /* -------------------------------------- */ typedef struct AnimationBasePathChange { diff --git a/source/blender/blenkernel/BKE_constraint.h b/source/blender/blenkernel/BKE_constraint.h index a3dd1319f0b..31f800aee15 100644 --- a/source/blender/blenkernel/BKE_constraint.h +++ b/source/blender/blenkernel/BKE_constraint.h @@ -240,11 +240,10 @@ struct bConstraint *BKE_constraint_add_for_pose(struct Object *ob, const char *name, short type); -bool BKE_constraint_remove_ex(ListBase *list, struct Object *ob, struct bConstraint *con); /** * Remove the specified constraint from the given constraint stack. */ -bool BKE_constraint_remove(ListBase *list, struct bConstraint *con); +bool BKE_constraint_remove_ex(ListBase *list, struct Object *ob, struct bConstraint *con); /** * Apply the specified constraint in the given constraint stack. diff --git a/source/blender/blenkernel/intern/anim_data.cc b/source/blender/blenkernel/intern/anim_data.cc index d57f55b2645..b8628fd7ce3 100644 --- a/source/blender/blenkernel/intern/anim_data.cc +++ b/source/blender/blenkernel/intern/anim_data.cc @@ -1216,6 +1216,29 @@ bool BKE_animdata_fix_paths_remove(ID *id, const char *prefix) return any_removed; } +bool BKE_animdata_driver_path_remove(ID *id, const char *prefix) +{ + AnimData *adt = BKE_animdata_from_id(id); + if (!adt) { + return false; + } + + const bool any_removed = fcurves_path_remove_from_listbase(prefix, &adt->drivers); + return any_removed; +} + +bool BKE_animdata_drivers_remove_for_rna_struct(ID &owner_id, StructRNA &type, void *data) +{ + PointerRNA constraint_ptr = RNA_pointer_create_discrete(&owner_id, &type, data); + const std::optional base_path = RNA_path_from_ID_to_struct(&constraint_ptr); + if (!base_path.has_value()) { + /* The data should exist, so the path should always resolve. */ + BLI_assert_unreachable(); + } + + return BKE_animdata_driver_path_remove(&owner_id, base_path.value().c_str()); +} + /* Apply Op to All FCurves in Database --------------------------- */ /* Helper for adt_apply_all_fcurves_cb() - Apply wrapped operator to list of F-Curves */ diff --git a/source/blender/blenkernel/intern/constraint.cc b/source/blender/blenkernel/intern/constraint.cc index ca1ed807d9e..13d15e7d7a2 100644 --- a/source/blender/blenkernel/intern/constraint.cc +++ b/source/blender/blenkernel/intern/constraint.cc @@ -73,6 +73,8 @@ #include "BIK_api.h" +#include "RNA_prototypes.hh" + #include "DEG_depsgraph.hh" #include "DEG_depsgraph_query.hh" @@ -5729,7 +5731,7 @@ void BKE_constraints_free(ListBase *list) BKE_constraints_free_ex(list, true); } -bool BKE_constraint_remove(ListBase *list, bConstraint *con) +static bool constraint_remove(ListBase *list, bConstraint *con) { if (con) { BKE_constraint_free_data(con); @@ -5742,8 +5744,10 @@ bool BKE_constraint_remove(ListBase *list, bConstraint *con) bool BKE_constraint_remove_ex(ListBase *list, Object *ob, bConstraint *con) { + BKE_animdata_drivers_remove_for_rna_struct(ob->id, RNA_Constraint, con); + const short type = con->type; - if (BKE_constraint_remove(list, con)) { + if (constraint_remove(list, con)) { /* ITASC needs to be rebuilt once a constraint is removed #26920. */ if (ELEM(type, CONSTRAINT_TYPE_KINEMATIC, CONSTRAINT_TYPE_SPLINEIK)) { BIK_clear_data(ob->pose); diff --git a/source/blender/blenkernel/intern/object.cc b/source/blender/blenkernel/intern/object.cc index cde2580f7cd..7c4e2f931fd 100644 --- a/source/blender/blenkernel/intern/object.cc +++ b/source/blender/blenkernel/intern/object.cc @@ -138,6 +138,8 @@ #include "ANIM_action_legacy.hh" +#include "RNA_prototypes.hh" + #ifdef WITH_PYTHON # include "BPY_extern.hh" #endif @@ -4460,6 +4462,8 @@ bool BKE_object_shapekey_remove(Main *bmain, Object *ob, KeyBlock *kb) return false; } + BKE_animdata_drivers_remove_for_rna_struct(key->id, RNA_ShapeKey, kb); + kb_index = BLI_findindex(&key->block, kb); BLI_assert(kb_index != -1); diff --git a/source/blender/editors/object/object_constraint.cc b/source/blender/editors/object/object_constraint.cc index ba9b545276d..bbf294c0df1 100644 --- a/source/blender/editors/object/object_constraint.cc +++ b/source/blender/editors/object/object_constraint.cc @@ -2761,7 +2761,7 @@ static int pose_ik_clear_exec(bContext *C, wmOperator * /*op*/) for (con = static_cast(pchan->constraints.first); con; con = next) { next = con->next; if (con->type == CONSTRAINT_TYPE_KINEMATIC) { - BKE_constraint_remove(&pchan->constraints, con); + BKE_constraint_remove_ex(&pchan->constraints, ob, con); } } pchan->constflag &= ~(PCHAN_HAS_IK | PCHAN_HAS_NO_TARGET); diff --git a/source/blender/editors/object/object_modifier.cc b/source/blender/editors/object/object_modifier.cc index 4cefb3766f2..048101ad04d 100644 --- a/source/blender/editors/object/object_modifier.cc +++ b/source/blender/editors/object/object_modifier.cc @@ -385,6 +385,8 @@ static bool object_modifier_remove( ob->mode &= ~OB_MODE_PARTICLE_EDIT; } + BKE_animdata_drivers_remove_for_rna_struct(ob->id, RNA_Modifier, md); + BKE_modifier_remove_from_list(ob, md); BKE_modifier_free(md); BKE_object_free_derived_caches(ob); diff --git a/source/blender/editors/object/object_relations.cc b/source/blender/editors/object/object_relations.cc index 436e8b6a672..7bab504f690 100644 --- a/source/blender/editors/object/object_relations.cc +++ b/source/blender/editors/object/object_relations.cc @@ -1199,7 +1199,7 @@ static int object_track_clear_exec(bContext *C, wmOperator *op) CONSTRAINT_TYPE_LOCKTRACK, CONSTRAINT_TYPE_DAMPTRACK)) { - BKE_constraint_remove(&ob->constraints, con); + BKE_constraint_remove_ex(&ob->constraints, ob, con); } } diff --git a/source/blender/makesrna/intern/rna_object.cc b/source/blender/makesrna/intern/rna_object.cc index f5e85c86a5f..e97ffc5dfba 100644 --- a/source/blender/makesrna/intern/rna_object.cc +++ b/source/blender/makesrna/intern/rna_object.cc @@ -1605,7 +1605,7 @@ static void rna_Object_constraints_remove(Object *object, return; } - BKE_constraint_remove(&object->constraints, con); + BKE_constraint_remove_ex(&object->constraints, object, con); con_ptr->invalidate(); blender::ed::object::constraint_update(bmain, object); diff --git a/source/blender/makesrna/intern/rna_pose.cc b/source/blender/makesrna/intern/rna_pose.cc index 1deb9623a17..18475aac156 100644 --- a/source/blender/makesrna/intern/rna_pose.cc +++ b/source/blender/makesrna/intern/rna_pose.cc @@ -388,7 +388,7 @@ static void rna_PoseChannel_constraints_remove( return; } - BKE_constraint_remove(&pchan->constraints, con); + BKE_constraint_remove_ex(&pchan->constraints, ob, con); con_ptr->invalidate(); blender::ed::object::constraint_update(bmain, ob); diff --git a/tests/python/bl_animation_drivers.py b/tests/python/bl_animation_drivers.py index c6414aeccc9..dc290efaa50 100644 --- a/tests/python/bl_animation_drivers.py +++ b/tests/python/bl_animation_drivers.py @@ -194,6 +194,68 @@ class ContextViewLayerDriverTest(AbstractEmptyDriverTest, unittest.TestCase): self.assertPropValue('test_fallback', 321) +class SubDataDriverRemovalTest(AbstractEmptyDriverTest, unittest.TestCase): + + def test_remove_object_modifier(self): + # Removing a modifier with a driver should also delete the driver + modifier = self.obj.modifiers.new("test", 'ARRAY') + # No animation data means no drivers. + self.assertEqual(self.obj.animation_data, None) + self.obj.driver_add('modifiers["test"].count') + self.assertEqual(len(self.obj.animation_data.drivers), 1) + self.obj.modifiers.remove(modifier) + self.assertEqual(len(self.obj.modifiers), 0) + self.assertEqual(len(self.obj.animation_data.drivers), 0, + "Removing the modifier should remove the driver on it") + + def test_remove_object_constraint(self): + # Using limit distance constraint because that has a property that can have a driver. + constraint = self.obj.constraints.new('LIMIT_DISTANCE') + constraint.name = "test" + # No animation data means no drivers. + self.assertEqual(self.obj.animation_data, None) + self.obj.driver_add('constraints["test"].distance') + self.assertEqual(len(self.obj.animation_data.drivers), 1) + self.obj.constraints.remove(constraint) + self.assertEqual(len(self.obj.constraints), 0) + self.assertEqual(len(self.obj.animation_data.drivers), 0, + "Removing the constraint should remove the driver on it") + + def test_remove_bone_constraint(self): + arm = bpy.data.armatures.new('Armature') + arm_ob = bpy.data.objects.new('ArmObject', arm) + bpy.context.scene.collection.objects.link(arm_ob) + bpy.context.view_layer.objects.active = arm_ob + bpy.ops.object.mode_set(mode='EDIT') + ebone = arm.edit_bones.new(name="test") + ebone.tail = (1, 0, 0) + bpy.ops.object.mode_set(mode='POSE') + pose_bone = arm_ob.pose.bones["test"] + constraint = pose_bone.constraints.new('LIMIT_DISTANCE') + constraint.name = "test" + self.assertEqual(len(pose_bone.constraints), 1) + arm_ob.driver_add('pose.bones["test"].constraints["test"].distance') + self.assertEqual(len(arm_ob.animation_data.drivers), 1) + pose_bone.constraints.remove(constraint) + self.assertEqual(len(pose_bone.constraints), 0) + self.assertEqual(len(arm_ob.animation_data.drivers), 0, + "Removing the constraint should remove the driver on it") + + def test_remove_shapekey(self): + self.obj.shape_key_add(name="base") + test_key = self.obj.shape_key_add(name="test") + # Due to the weirdness of shapekeys, this is an ID. + shape_key_id = self.obj.data.shape_keys + self.assertEqual(len(shape_key_id.key_blocks), 2) + shape_key_id.driver_add('key_blocks["test"].value') + self.assertEqual(len(shape_key_id.animation_data.drivers), 1) + + self.obj.shape_key_remove(test_key) + self.assertEqual(len(shape_key_id.key_blocks), 1) + self.assertEqual(len(shape_key_id.animation_data.drivers), 0, + "Removing the shape key should remove any driver on it") + + def main(): global args import argparse