an.timing.timeline#
Timeline: tracks of placed clips with absolute times — the compiled evaluation form.
A Timeline is a flat description of what plays when: tracks of
PlacedClip, each a Clip at an absolute start,
with a duration override, a speed and recorded blend ramps. It is the level the
stage runtime evaluates (runtime.js::evaluateTimeline is a port of
evaluate_timeline()) and the level the contract’s golden vectors exercise
(compiled.schema.json). Authoring composition trees (an.ir.compose) are
flattened into it by a compiler.
blend_in and blend_out ramps are recorded but not applied to pose
values: the timeline produces the raw pose.
>>> from an.timing.channel import Channel, Keyframe
>>> from an.timing.clip import Clip
>>> ch = Channel("a", "x", [Keyframe(0.0, 0.0), Keyframe(1.0, 10.0)])
>>> clip = Clip("walk", duration=1.0, channels=[ch])
>>> tl = Timeline(duration=2.0, tracks=[Track("a", clips=[PlacedClip(clip, start_time=0.5)])])
>>> evaluate_timeline(tl, 1.0)[("a", "x")]
5.0
Functions
|
What |
|
Evaluate |
|
One compiled animation ( |
The compiled document's |
Classes
|
A clip placed at an absolute time on a track. |
|
A sequence of placed clips that share a common purpose / target prefix. |
|
A duration + ordered list of tracks. |
- class an.timing.timeline.PlacedClip(clip, start_time=0.0, duration=None, speed=1.0, blend_in=0.0, blend_out=0.0)[source]#
Bases:
objectA clip placed at an absolute time on a track.
- class an.timing.timeline.Timeline(duration, tracks=<factory>, space=None)[source]#
Bases:
objectA duration + ordered list of tracks. The canonical playback structure.
spaceis the property space the timeline’s DOCUMENT declares for its targets (an#245): one space, a registered name, or atarget -> spaceresolver. It is whatevaluate_timeline()uses when the caller passes none, so every caller of the default evaluates a target in the space its entity’s kind declares.None(every document that declares nothing) is the kernel default,an.timing.spaces.DFLT_TIMELINE_SPACE.
- class an.timing.timeline.Track(target_root='', clips=<factory>)[source]#
Bases:
objectA sequence of placed clips that share a common purpose / target prefix.
target_rootis informational metadata for downstream tools (the JS runtime can use it to scope rendering); evaluation does not filter by it.
- an.timing.timeline.clip_from_json(anim, *, name=None)[source]#
One compiled animation (
compiled.schema.json’sanimation) as aClip.Accepts the JSON mapping or any object with the same attributes (the stage’s
AnimationClipJSON). Two fields are carried rather than defaulted, and both have cost a bug:loop_mode(without it every loop evaluated asonce— an#7) and a list-valuedeasing, which is a cubic-bezier control quadruple and must stay a tuple forKeyframe. The stage compiler reads a from-less tween’s start through this too (an#212), so it evaluates exactly what the runtime will.- Return type:
- an.timing.timeline.evaluate_timeline(timeline, t, *, space=None)[source]#
Evaluate
timelineat timet, merging poses across tracks/clips.The result is a PURE function of
t(an#185): what a node shows attnever depends on which instants were evaluated before it. Per(target, property):Active — some clip writing it is playing at
t(inclusive end: a clip at[s, e]is active att == etoo, so the final frame of “play this from 0 to 1 s” is visible at 1.0). Later wins: track order, then clip order within a track. Written att.Held — no clip writing it is playing, but one has ended: the value the clip reached AT ITS END holds. The latest end wins; a tie goes to the later clip, the same “later wins” as above. Written at that end.
At rest — nothing writing it has started yet. The key is ABSENT from the pose, and its value is the node’s own (the entity’s rest state;
runtime.jsrestores what it built).
Keys that write the same thing on one node (a write group: the swap sets of one visual,
rotation/rotation_rad) keep only the most recently WRITTEN — an endedviseme@happyspan does not outlive thevisemetrack that took the mouth back.spacesays what each property is (an.timing.spaces): one space, a registered space’s name, or atarget -> spaceresolver. Its field kinds interpolate and its write groups resolve.Noneis the default space,an.timing.spaces.DFLT_TIMELINE_SPACE(the stage node’s declared kinds, by name at call time).space=VALUE_TYPEDis the ruleruntime.jsimplements — interpolation by value type, thestage.nodewrite groups — which gives the same pose on every timeline the compiler emits (it refuses a value that fails its field kind).Forward-order rendering used to show the value at the clip’s last SAMPLED frame instead (the runtime kept whatever it last applied). The two agree whenever a clip ends on the frame grid — true of every golden-corpus clip — and differ when it ends between frames: a 0.37 s tween to 10 at 24 fps used to stop at 9.80 and now lands on 10, as authored. That landing is deliberate (it is the bug the motion presets’ settling
setpatched one preset at a time), and it is what makes the pose independent of the grid. Also deliberate: a clip shorter than a frame that no frame lands in now leaves its end value, and a held descendant tint stays on top of an ancestor’s later tint (the more specific target wins, as it always did while both played).runtime.js::evaluateTimelineis a port of this function andcutan’stests/test_pure_pose.pyholds the two to it.>>> from an.timing.channel import Channel, Keyframe >>> from an.timing.clip import Clip >>> ch = Channel("a", "x", [Keyframe(0.0, 0.0), Keyframe(1.0, 10.0)]) >>> tl = Timeline(2.0, [Track("a", [PlacedClip(Clip("m", 1.0, [ch]), 0.5)])]) >>> evaluate_timeline(tl, 0.0) # not started: at rest, so absent {} >>> evaluate_timeline(tl, 1.0)[("a", "x")] # active 5.0 >>> evaluate_timeline(tl, 1.75)[("a", "x")] # ended: its end value holds 10.0
- an.timing.timeline.timeline_from_compiled(doc)[source]#
The compiled document’s
timeline/animationsas an evaluableTimeline.docis the JSON mapping ofcompiled.schema.jsonor any object with the same attributes (the stage’sCutoutSceneJSON). This is the Python side of the parity contract:evaluate_timelineover what it returns is the executable specruntime.jsis tested against.target_rootand the blend ramps are carried although nothing reads them yet: a reader that quietly drops a field it was handed is a lossy “rebuilds the evaluable form”.- Return type:
>>> doc = {"timeline": {"duration": 1.0, "tracks": [{"clips": [ ... {"animation_id": "m", "start_time": 0.0}]}]}, ... "animations": {"m": {"duration": 1.0, "channels": [{"target": "a", ... "property": "x", "keyframes": [{"time": 0.0, "value": 0.0}, ... {"time": 1.0, "value": 4.0}]}]}}} >>> evaluate_timeline(timeline_from_compiled(doc), 0.25) {('a', 'x'): 1.0}
- an.timing.timeline.write_group(prop)[source]#
What
propwrites on a STAGE node (thestage.nodespace’s groups).Two keys in one group set the same thing, so only the more recently written can be showing: every swap set on a node swaps the one visual it carries (
visemeandviseme@happyboth set the mouth’s texture, an#88), androtation_radisrotation. Every other runtime property (an.base.TRANSFORM_PROPERTIES, the runtime’s own switch) writes only itself.- Return type:
>>> write_group("x"), write_group("rotation_rad"), write_group("viseme@happy") ('x', 'rotation', '<swap>')