Anim: Add "replace" mix mode to the Action Constraint

The available mix modes on the Action Constraint only allowed
*combining* the Action's transforms with the input transforms, but
unlike most other constraints lacked a way to completely
override/replace those transforms.

This PR adds a "Replace" mix mode to the Action Constraint, bringing it
in line with most of the other constraints already in Blender.

![action_constraint_replace_mode_screenshot.png](/attachments/51fb09d6-0a87-42dc-a75e-9ae81c856796)

----

Test file: [action_constraint_replace_mode.blend](/attachments/fc3417a8-b60a-4212-9840-5b59191e9ed9)

- The small bone at the top is the action constraint target (translating it right-left triggers the action constraint).
- Both two-bone chains are set up with action constraints.  The base bones of each chain additionally have a copy location constraint to the small sideways bone, placed before the action constraint in their constraint stack.
- The chain on the left has the default mix mode, which allows you to manipulate the bones on top of what the action constraint does, and allows the copy location constraint on the base bone to work.
- The bones on the right have the new "Replace" mix mode, and therefore manipulating them does not affect the final constrained transformation, and the copy location on the base bone is overridden by the action constraint.

Pull Request: https://projects.blender.org/blender/blender/pulls/138316
This commit is contained in:
Nathan Vegdahl
2025-05-15 14:30:01 +02:00
committed by Nathan Vegdahl
parent e4e49e1634
commit 8a984f4f4e
4 changed files with 54 additions and 0 deletions

View File

@@ -460,6 +460,47 @@ class ActionConstraintTest(AbstractConstraintTests):
con.action_slot,
"Assigning an Action with a virgin slot should automatically select that slot")
def test_mix_modes(self):
owner = bpy.context.scene.objects["Action.owner"]
target = bpy.context.scene.objects["Action.target"]
action = bpy.data.actions.new("Slotted")
slot = action.slots.new('OBJECT', "Slot")
layer = action.layers.new(name="Layer")
strip = layer.strips.new(type='KEYFRAME')
strip.key_insert(slot, "location", 0, 2.0, 0.0)
strip.key_insert(slot, "location", 0, 7.0, 10.0)
con = owner.constraints["Action"]
con.action = action
con.action_slot = slot
con.transform_channel = 'LOCATION_X'
con.min = -1.0
con.max = 1.0
con.frame_start = 0
con.frame_end = 10
# Set the constrained object's location to something other than [0,0,0],
# so we can verify that it's actually replaced/mixed as appropriate to
# the mix mode.
owner.location = (2.0, 3.0, 4.0)
con.mix_mode = 'REPLACE'
self.matrix_test("Action.owner", Matrix((
(1.0, 0.0, 0.0, 4.5),
(0.0, 1.0, 0.0, 0.0),
(0.0, 0.0, 1.0, 0.0),
(0.0, 0.0, 0.0, 1.0)
)))
con.mix_mode = 'BEFORE_SPLIT'
self.matrix_test("Action.owner", Matrix((
(1.0, 0.0, 0.0, 6.5),
(0.0, 1.0, 0.0, 3.0),
(0.0, 0.0, 1.0, 4.0),
(0.0, 0.0, 0.0, 1.0)
)))
def main():
global args