Fix #110650: Drivers not removed if removing their container

This applies to modifiers, constraints and shape keys.
Any driver on such data was not removed with it,
leaving invalid drivers on objects.

With this patch, the drivers are removed, but animation is left untouched.
This is because animation might be reused somewhere else and we don't
want to introduce potential side effects there.

This also adds unit tests for the fixed cases

Pull Request: https://projects.blender.org/blender/blender/pulls/134511
This commit is contained in:
Christoph Lendenfeld
2025-02-25 12:11:15 +01:00
committed by Christoph Lendenfeld
parent ba149680a2
commit 5100a80f7f
11 changed files with 121 additions and 8 deletions

View File

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