an.ir.compose#
Composition combinators for authoring action trees, plus a flattener.
The composition tree is the authoring form. The flat list of `FlatAction`s with absolute start times is the canonical form that gets verified, cached, and rendered. Both forms round-trip through the schema; the flat form is what tooling reasons about.
>>> from an.ir.compose import sequence, parallel, tween, delay, flatten
>>> action = sequence(
... tween("charlie/torso", "rotation", to=10.0, duration=1.0),
... delay(0.5),
... tween("charlie/torso", "rotation", to=0.0, duration=1.0),
... )
>>> flat = flatten(action)
>>> [round(f.start, 3) for f in flat]
[0.0, 1.5]
>>> [round(f.end, 3) for f in flat]
[1.0, 2.5]
>>> action2 = parallel(
... tween("a", "x", to=1.0, duration=2.0),
... tween("b", "y", to=1.0, duration=3.0),
... )
>>> flat2 = flatten(action2)
>>> [(round(f.start, 2), round(f.end, 2)) for f in flat2]
[(0.0, 2.0), (0.0, 3.0)]
Module Attributes
|
|
|
Functions
|
An empty span that consumes time. |
|
Compute the total duration of an action tree without evaluating it. |
|
Walk a composition tree, emitting leaf actions with absolute times. |
|
|
|
The registered |
|
Repeat |
|
|
|
Run all children at once. |
|
|
|
Run children one after the other. |
|
Discrete property set at time |
|
Start each action |
|
Animate a property from |
Classes
|
A leaf action with its absolute start and end times. |
|
What a kind's |
- class an.ir.compose.FlatAction(start, end, action)[source]#
Bases:
objectA leaf action with its absolute start and end times.
The flat-form list is the canonical representation passed to renderers and verifiers. Composition nodes (sequence/parallel/delay/loop) do not appear in the flat form — they’re collapsed into time offsets.
- class an.ir.compose.FlattenContext(out, extent=None)[source]#
Bases:
objectWhat a kind’s
flattenhook gets: where to put leaves, how to recurse.extentis the caller’s extent resolver (PlayExtent), passed on to every leaf’sdurationhook.
- an.ir.compose.INHERIT = INHERIT#
tween(..., easing=INHERIT)— the default — leaves the easing UNSET, so the scene’smeta.default_easingapplies, else"ease_in_out"(an#166). A sentinel rather thanNonebecauseNonealready means linear.
- an.ir.compose.PlayExtent#
PlayAction -> secondsa play WITHOUT an explicitdurationoccupies in asequence. The default isdefault_play_extent(); the compiler andan validatepass one bound to the entity’s descriptor. Generically, it is the caller’s extent resolver: it is handed to every leaf kind’sdurationhook (an.genres.ActionKind), and the kinds that have an open-ended length (the cut-out genre’splay) consult it.
- an.ir.compose.delay(duration)[source]#
An empty span that consumes time. Useful inside
sequence.- Return type:
- an.ir.compose.duration_of(action, *, play_extent=None)[source]#
Compute the total duration of an action tree without evaluating it.
play_extentresolves a duration-lessplay(seePlayExtent).- Return type:
>>> duration_of(tween("a", "x", to=1.0, duration=2.0)) 2.0 >>> duration_of(sequence(delay(0.5), tween("a", "x", to=1.0, duration=1.5))) 2.0 >>> duration_of(parallel(delay(0.5), delay(2.5))) 2.5 >>> duration_of(loop(delay(0.25), 4)) 1.0 >>> duration_of(set_("a", "x", 1.0)) 0.0
- an.ir.compose.flatten(action, *, start=0.0, play_extent=None)[source]#
Walk a composition tree, emitting leaf actions with absolute times.
A
playwithoutdurationadvances asequencebyplay_extent(defaultdefault_play_extent()): its natural length, or zero for a looping animation, which runs to the shot end.Delays are absorbed into the timeline (they don’t appear in the output). Loops are unrolled by simple repetition — appropriate at v0.1; the cutout runtime can re-roll for efficiency later.
Every node is dispatched through its registered kind (
ActionKind), so a genre’s kind flattens without an edit here; a node whose kind nobody registered raises, naming the genre that provides it, and anExtensionActionread before its genre loaded is validated by the registered model on the way through.- Return type:
- an.ir.compose.iter_actions(action)[source]#
actionand every action under it, depth first (composites through their kind’schildrenhook). Unregistered kinds are yielded, not raised: a validator walks with this to REPORT them.>>> [a.kind for a in iter_actions(sequence(delay(1.0), loop(delay(0.5), 2)))] ['sequence', 'delay', 'loop', 'delay']
- an.ir.compose.kind_of(action)[source]#
The registered
ActionKindthat governsaction.Raises
UnregisteredKindError, naming the genre that provides it, for a kind nobody registered.- Return type:
>>> kind_of(delay(1.0)).name 'delay'
- an.ir.compose.map_leaves(action, fn)[source]#
actionwith every LEAF replaced byfn(leaf), composites rebuilt around them (whatever field a composite keeps its children in: its kind’schildrenhook says which values are children, the model’s fields say where they live, soloop.childand a genre’s composite are reached).- Return type:
>>> act = sequence(delay(1.0), loop(set_("a", "x", 1.0), 2)) >>> renamed = map_leaves(act, lambda a: a.model_copy(update={"target": "b"}) if a.kind == "set" else a) >>> [a.target for a in iter_actions(renamed) if a.kind == "set"] ['b']
- an.ir.compose.parallel(*actions)[source]#
Run all children at once. Total duration = max of child durations.
- Return type:
- an.ir.compose.resolve_action(action)[source]#
actionas its registered model (anExtensionActionleft open by a document read before its genre loaded is validated now).- Return type:
>>> resolve_action(delay(0.5)).duration 0.5
- an.ir.compose.sequence(*actions)[source]#
Run children one after the other. Total duration = sum of child durations.
- Return type:
- an.ir.compose.set_(target, property, value, *, at=0.0)[source]#
Discrete property set at time
at(relative to its enclosing scope).- Return type:
- an.ir.compose.stagger(lag, *actions)[source]#
Start each action
lagseconds after the previous one STARTS.The stagger (Manim’s
LaggedStart,previz’s compose, a crowd entering one by one): the children run in parallel, thei-th delayed byi * lag. It is authoring sugar, not a new kind — it builds theparallelofsequence(delay(i * lag), action)it means, so the scene document,scene.mdand every renderer see only core kinds. Total duration: the latest child’s end.scene.mdholds it verbatim (akind: parallelentry), so it round-trips. (an.stage.text.reveal_units—an.stage.text.staggerbefore an#241 — is the text-block preset: a LIST of per-unit actions with holds, not a combinator.)- Return type:
>>> flat = flatten(stagger(0.25, tween("a", "x", to=1.0, duration=1.0), ... tween("b", "x", to=1.0, duration=1.0), ... tween("c", "x", to=1.0, duration=1.0))) >>> [(f.action.target, f.start, f.end) for f in flat] [('a', 0.0, 1.0), ('b', 0.25, 1.25), ('c', 0.5, 1.5)] >>> duration_of(stagger(0.5, delay(1.0), delay(1.0))) 1.5 >>> stagger(0.1).children [] >>> stagger(-1.0, delay(1.0)) Traceback (most recent call last): ... ValueError: stagger lag must be >= 0, got -1.0
- an.ir.compose.tween(target, property, to, duration, *, from_=None, easing=INHERIT)[source]#
Animate a property from
from_(or its current value) toto.easingleft out inherits the scene’smeta.default_easing(else"ease_in_out"); naming one —"ease_in_out"included — pins it.- Return type:
>>> "easing" in tween("a", "x", to=1.0, duration=1.0).model_fields_set False >>> tween("a", "x", to=1.0, duration=1.0, easing="linear").easing 'linear'