anim_stack

class janim.anims_core.anim_stack.AnimStack(item: Item, time_aligner: TimeAligner)

Bases: object

Used to record the StackableAnimation objects applied to a specific Item, forming a stack

The core logic of JAnim animations:

Each later animation is applied on top of the result of the previous one.

This is the internal logic behind JAnim’s animation combination feature; we refer to this internal structure as the animation stack.

get_at_left_ctx: ContextVar = <ContextVar name='AnimStack.get_at_left_ctx' default=False>

Used to mark the behavior of compute() when using get_at_left

with ContextSetter(AnimStack.get_at_left_ctx, True):
    stack.compute(...)

Using this ContextVar instead of directly setting parameters for compute() allows nested calls to also be marked as using get_at_left for processing

clear_cache() None
display(global_t: float) Display

Records the current state of the item into the animation stack, making it display this state after global_t

set_latest_display(anim: DisplayType) None
detect_change(global_t: float) None

If the item may have changed compared to the state recorded by self._prev_display, records the new state into the stack after global_t

may_changed() bool

Checks whether the item may have changed compared to the state recorded by self._prev_display

add(anim: StackableAnimation, *, _is_display: bool = False) None

Adds a StackableAnimation object to the stack

Parameters:

anim – The animation object to add to the stack

Strategy:

First, for an intuitive understanding, we can view the timeline as a strip-shaped canvas where paint can continuously be layered. Each time we use this method to add an animation object, it is like drawing on this canvas according to the animation’s start and end times

As a result, different animation objects are layered over different intervals of the timeline, and different objects take effect in different intervals

To determine which animation objects are active at a given time t, we cannot simply iterate through all animations that have ever been applied, as this would be far too inefficient

Therefore, this item’s animation stack is divided into multiple chunks according to the start and end times of each animation. We only need to use binary search to find the chunk containing t to determine which animations are active at that moment

For example:

If we have two animations in sequence, A 0~3s and B 1~2s

Initially:

starts:  0
chunks: [ ]

After adding A first:

starts:  0     |  3
chunks: [A]    | [ ]

After adding B:

starts:  0     |  1     |  2     |  3
chunks: [A]    | [A,B]  | [A]    | [ ]

As can be seen, to add B, we split the timeline at 1s and 2s respectively

If we then add C 2s~FOREVER:

starts:  0     |  1     |  2     |  3
chunks: [A]    | [A,B]  | [A,C]  | [C]

Note: Since all Animation objects are processed by align_anim_or_record(), there is no need to worry about unnecessary fine-grained splits caused by minor floating-point errors

Note: During addition, most cases work as shown above. However, if the animation’s _order is more complex, we need to insert later animations into the existing animation stack rather than simply appending them, in order to ensure that _order remains in ascending order within each chunk

get_at_left(global_t: float) list[StackableAnimation]

Gets the chunk containing global_t (that is, the list of animations active at global_t)

Unlike get(), this method treats chunk intervals as left-open and right-closed (that is, when at a boundary point, it returns the chunk on the left). This additional method is useful when calculating become_at_end for certain animations

Parameters:

global_t – Global time

get(global_t: float) list[StackableAnimation]

Gets the chunk containing global_t (that is, the list of animations active at global_t)

At interval boundaries, follows the principle that chunk intervals are left-closed and right-open (that is, at a boundary point, the chunk on the right is returned)

Parameters:

global_t – Global time

compute(global_t: float, readonly: bool) Item

Gets the item after applying animation effects at global_t

Parameters:
  • global_t – Global time

  • readonly – Indicates whether the caller will modify the return value. If readonly=True, it means the value will not be modified, and this method directly returns the cache itself. However, this is not a strict restriction; when passing readonly=True, the caller must follow the principle of not modifying the return value to avoid affecting cached data. If readonly=False, it means the value may be modified, so a copy of the cache is returned to avoid affecting cached data. For example: - The call in item_current() uses readonly=False, because its return value will eventually be used by the user, and we cannot guarantee whether the user will modify it. - The call used for rendering uses readonly=True, because rendering does not modify item data.

By default, get() is used to obtain the animation stack. You can set get_at_left_ctx to use get_at_left() instead. For details, see the documentation of get_at_left_ctx

For details about the mechanisms of this method, see the descriptions of StackableAnimation, ApplyParams, and ApplyAligner

For implementation details of this method, see the comments in the _compute code

janim.anims_core.anim_stack.simplify_anim_stacks(stacks: list[AnimStack]) None

Called after the construction of Timeline is completed. It is used to check whether the AnimStack instances in the Timeline contain components that can be simplified

  • When no ApplyAligner appears, each animation stack only needs to retain the animations from the last DisplayType onwards

  • When a ApplyAligner appears, dependencies between animation stacks may occur, so the previous simplification cannot be applied directly, as it would break the ApplyAligner that may be depended on. In this case, all ApplyAligner instances within the dependencies need to be analyzed to determine whether they can be removed together