anim_stack¶
- class janim.anims_core.anim_stack.AnimStack(item: Item, time_aligner: TimeAligner)¶
Bases:
objectUsed to record the
StackableAnimationobjects applied to a specificItem, forming a stackThe 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 usingget_at_leftwith ContextSetter(AnimStack.get_at_left_ctx, True): stack.compute(...)
Using this
ContextVarinstead of directly setting parameters forcompute()allows nested calls to also be marked as usingget_at_leftfor 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 afterglobal_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
StackableAnimationobject 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 inefficientTherefore, 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
tto determine which animations are active at that momentFor example:
If we have two animations in sequence,
A 0~3sandB 1~2sInitially:
starts: 0 chunks: [ ]
After adding
Afirst: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 at1sand2srespectivelyIf we then add
C 2s~FOREVER:starts: 0 | 1 | 2 | 3 chunks: [A] | [A,B] | [A,C] | [C]
Note: Since all
Animationobjects are processed byalign_anim_or_record(), there is no need to worry about unnecessary fine-grained splits caused by minor floating-point errorsNote: During addition, most cases work as shown above. However, if the animation’s
_orderis more complex, we need to insert later animations into the existing animation stack rather than simply appending them, in order to ensure that_orderremains 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 atglobal_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 calculatingbecome_at_endfor 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 atglobal_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 passingreadonly=True, the caller must follow the principle of not modifying the return value to avoid affecting cached data. Ifreadonly=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 initem_current()usesreadonly=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 usesreadonly=True, because rendering does not modify item data.
By default,
get()is used to obtain the animation stack. You can setget_at_left_ctxto useget_at_left()instead. For details, see the documentation ofget_at_left_ctxFor details about the mechanisms of this method, see the descriptions of
StackableAnimation,ApplyParams, andApplyAlignerFor implementation details of this method, see the comments in the
_computecode
- janim.anims_core.anim_stack.simplify_anim_stacks(stacks: list[AnimStack]) None¶
Called after the construction of
Timelineis completed. It is used to check whether theAnimStackinstances in theTimelinecontain components that can be simplifiedWhen no
ApplyAlignerappears, each animation stack only needs to retain the animations from the lastDisplayTypeonwardsWhen a
ApplyAlignerappears, dependencies between animation stacks may occur, so the previous simplification cannot be applied directly, as it would break theApplyAlignerthat may be depended on. In this case, allApplyAlignerinstances within the dependencies need to be analyzed to determine whether they can be removed together