File: bl_animation_nla_strip.py

package info (click to toggle)
blender 4.3.2%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 309,564 kB
  • sloc: cpp: 2,385,210; python: 330,236; ansic: 280,972; xml: 2,446; sh: 972; javascript: 317; makefile: 170
file content (118 lines) | stat: -rw-r--r-- 4,980 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later

"""
Tests the evaluation of NLA strips based on their properties and placement on NLA tracks.

blender -b --factory-startup --python tests/python/bl_animation_nla_strip.py
"""

import bpy
import sys
import unittest


class AbstractNlaStripTest(unittest.TestCase):
    """ Sets up a series of strips in one NLA track. """

    test_object: bpy.types.Object = None
    """ Object whose X Location is animated to check strip evaluation. """

    nla_tracks: bpy.types.NlaTracks = None
    """ NLA tracks of the test object, which are cleared after each test case. """

    action: bpy.types.Action = None
    """ Action with X Location keyed on frames 1 to 4 with the same value as the frame, with constant interpolation. """

    def setUp(self):
        bpy.ops.wm.read_factory_settings(use_empty=True)

        self.test_object = bpy.data.objects.new(name="Object", object_data=bpy.data.meshes.new("Mesh"))
        bpy.context.collection.objects.link(self.test_object)
        self.test_object.animation_data_create()

        self.nla_tracks = self.test_object.animation_data.nla_tracks

        self.action = bpy.data.actions.new(name="ObjectAction")
        x_location_fcurve = self.action.fcurves.new(data_path="location", index=0, action_group="Object Transforms")
        for frame in range(1, 5):
            x_location_fcurve.keyframe_points.insert(frame, value=frame).interpolation = "CONSTANT"

    def add_strip_no_extrapolation(self, nla_track: bpy.types.NlaTrack, start: int) -> bpy.types.NlaStrip:
        """ Places a new strip with the test action on the given track, setting extrapolation to nothing. """
        strip = nla_track.strips.new("ObjectAction", start, self.action)
        strip.extrapolation = "NOTHING"
        return strip

    def assertFrameValue(self, frame: float, expected_value: float):
        """ Checks the evaluated X Location at the given frame. """
        int_frame, subframe = divmod(frame, 1)
        bpy.context.scene.frame_set(frame=int(int_frame), subframe=subframe)
        self.assertEqual(expected_value, self.test_object.evaluated_get(
            bpy.context.evaluated_depsgraph_get()
        ).matrix_world.translation[0])


class NlaStripSingleTest(AbstractNlaStripTest):
    """ Tests the inner values as well as the boundaries of one strip on one track. """

    def test_extrapolation_nothing(self):
        """ Tests one strip with no extrapolation. """
        self.add_strip_no_extrapolation(self.nla_tracks.new(), 1)

        self.assertFrameValue(0.9, 0.0)
        self.assertFrameValue(1.0, 1.0)
        self.assertFrameValue(1.1, 1.0)
        self.assertFrameValue(3.9, 3.0)
        self.assertFrameValue(4.0, 4.0)
        self.assertFrameValue(4.1, 0.0)


class NlaStripBoundaryTest(AbstractNlaStripTest):
    """ Tests two strips, the second one starting when the first one ends. """

    # Incorrectly, the first strip is currently evaluated at the boundary between two adjacent strips (see #113487).
    @unittest.expectedFailure
    def test_adjacent(self):
        """ The second strip should be evaluated at the boundary between two adjacent strips. """
        nla_track = self.nla_tracks.new()
        self.add_strip_no_extrapolation(nla_track, 1)
        self.add_strip_no_extrapolation(nla_track, 4)

        self.assertFrameValue(3.9, 3.0)
        self.assertFrameValue(4.0, 1.0)
        self.assertFrameValue(4.1, 1.0)

    def test_adjacent_muted(self):
        """ The first strip should be evaluated at the boundary if it is adjacent to a muted strip. """
        nla_track = self.nla_tracks.new()
        self.add_strip_no_extrapolation(nla_track, 1)
        self.add_strip_no_extrapolation(nla_track, 4).mute = True

        self.assertFrameValue(3.9, 3.0)
        self.assertFrameValue(4.0, 4.0)
        self.assertFrameValue(4.1, 0.0)

    def test_first_above_second(self):
        """ The first strip should be evaluated at the boundary, when followed by another strip on a track below. """
        self.add_strip_no_extrapolation(self.nla_tracks.new(), 4)
        self.add_strip_no_extrapolation(self.nla_tracks.new(), 1)

        self.assertFrameValue(3.9, 3.0)
        self.assertFrameValue(4.0, 4.0)
        self.assertFrameValue(4.1, 1.0)

    def test_second_above_first(self):
        """ The second strip should be evaluated at the boundary, when preceded by another strip on a track below. """
        self.add_strip_no_extrapolation(self.nla_tracks.new(), 1)
        self.add_strip_no_extrapolation(self.nla_tracks.new(), 4)

        self.assertFrameValue(3.9, 3.0)
        self.assertFrameValue(4.0, 1.0)
        self.assertFrameValue(4.1, 1.0)


if __name__ == "__main__":
    # Drop all arguments before "--", or everything if the delimiter is absent. Keep the executable path.
    unittest.main(argv=sys.argv[:1] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []))