an.timing.kinds#

Field kinds: the declared interpolator of each animatable property.

The declared kind picks the interpolator; a runtime value never does. A field kind is what lets a number be an angle (shortest arc), a log-space zoom, or a colour mixed in OKLab — none of which a value’s type can say. Kinds are declared per property by a property space (an.timing.spaces); a property no space declares is DiscreteKind.

Seven kinds are seeded, each parametrised (the TypeScript kernel’s seven):

Discrete is defined on TIME, never on a ratio or an eased value (an#86). A segment from key a to key b shows b iff t >= a.time + switch_at * (b.time - a.time), and switch_at == 1 is evaluated as t >= b.time with no arithmetic at all: (t - a.time) / span can round up to 1.0 while t < b.time, and an overshooting easing would flip an eased comparison twice.

Genres add kinds with register_kind() (a Manim genre’s points, say) without editing this module.

>>> NumberKind().interpolate(0.0, 10.0, 0.25, Segment(0.25, 0.0, 1.0))
2.5
>>> NumberKind(space="log").interpolate(1.0, 4.0, 0.5, Segment(0.5, 0.0, 1.0))
2.0
>>> AngleKind().interpolate(350, 10, 0.5, Segment(0.5, 0.0, 1.0))  # not re-normalised
360.0
>>> b = 9.767899248713501
>>> DiscreteKind(switch_at=1).interpolate("A", "B", 1.0, Segment(math.nextafter(b, 0), 0.15, b))
'A'
>>> kind_from_spec({"kind": "number", "space": "log"})
NumberKind(space='log')

Module Attributes

DEFAULT_SWITCH_AT

Where an undeclared discrete value switches, in normalised segment time.

QUATERNION_UNIT_TOLERANCE

How far a quaternion's norm may be from 1 and still count as unit length.

SLERP_LINEAR_THRESHOLD

Above this |dot|, slerp falls back to normalised lerp (the arc is too short).

KindFactory

its parameters as keyword arguments -> an instance.

CORE_OWNER

Who registered the seeded kinds (only these reach the contract files).

Functions

kind_from_spec(spec)

A kind instance from its contract spec ({"kind": name, **params}), a bare kind name, or an instance (returned as is).

kind_names(*[, owner])

The registered kind names in registration order; only owner's when given.

register_kind(name, factory, *[, replace, owner])

Register a field kind under name (how a genre adds one).

shortest_delta(a, b[, period])

The signed shortest turn from a to b, in [-period/2, period/2].

slerp(a, b, u)

Spherical interpolation of unit quaternions [x, y, z, w], the short way.

Classes

AngleKind([unit, wrap])

An angle: the shortest arc when wrap, else the raw numbers.

ColorKind([space])

A colour (hex or [r, g, b, a?] in 0..1), written back in the form of the start value.

DiscreteKind([switch_at])

Holds a and switches to b on TIME (see the module docstring).

FieldKind()

Base of every field kind: a frozen, hashable, JSON-describable interpolator.

NumberKind([space])

A continuous scalar.

OrbitKind([unit])

A camera orbiting a target: {azimuth, elevation, distance, target?}.

QuaternionKind()

A unit quaternion [x, y, z, w], interpolated by slerp after a sign fix.

Segment(t, start, end)

Where an evaluation sits: time t inside the segment [start, end).

VectorKind()

A fixed-length list of numbers, interpolated componentwise.

Exceptions

FieldKindError

A kind spec is malformed, or names no registered kind.

class an.timing.kinds.AngleKind(unit='deg', wrap=True)[source]#

Bases: FieldKind

An angle: the shortest arc when wrap, else the raw numbers. Interpolated angles are not re-normalised (350 -> 10 passes through 360).

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

an.timing.kinds.CORE_OWNER: str = 'an'#

Who registered the seeded kinds (only these reach the contract files).

class an.timing.kinds.ColorKind(space='oklab')[source]#

Bases: FieldKind

A colour (hex or [r, g, b, a?] in 0..1), written back in the form of the start value. oklab mixes perceptually with premultiplied alpha; srgb lerps the encoded channels (Manim’s and the stage tint’s rule).

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

an.timing.kinds.DEFAULT_SWITCH_AT: float = 0.5#

Where an undeclared discrete value switches, in normalised segment time.

class an.timing.kinds.DiscreteKind(switch_at=0.5)[source]#

Bases: FieldKind

Holds a and switches to b on TIME (see the module docstring).

switch_at lies in (0, 1]. The default, 0.5, is the switch point of an undeclared property; a replacement drawing (a swap set) declares 1, so it never appears before its own key.

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

switches: ClassVar[bool] = True#

True when the value (or part of it) switches on TIME rather than moving.

class an.timing.kinds.FieldKind[source]#

Bases: object

Base of every field kind: a frozen, hashable, JSON-describable interpolator.

Subclasses set name and implement interpolate(); the dataclass fields are the kind’s parameters, and to_spec() writes them as the contract does ({"kind": name, **non-default params}).

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

switches: ClassVar[bool] = False#

True when the value (or part of it) switches on TIME rather than moving.

exception an.timing.kinds.FieldKindError[source]#

Bases: ValueError

A kind spec is malformed, or names no registered kind.

an.timing.kinds.KindFactory#

its parameters as keyword arguments -> an instance.

Type:

A kind’s factory

alias of Callable[[…], FieldKind]

class an.timing.kinds.NumberKind(space='linear')[source]#

Bases: FieldKind

A continuous scalar. space="log" interpolates the logarithm (zoom, scale).

Linear interpolation is a + (b - a) * u with no special case at the ends, which is the stage runtime’s arithmetic: a declared number channel is bit-identical to what runtime.js draws.

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

class an.timing.kinds.OrbitKind(unit='deg')[source]#

Bases: FieldKind

A camera orbiting a target: {azimuth, elevation, distance, target?}.

Azimuth takes the shortest arc, elevation is lerped and clamped to the poles, distance is lerped in log, target componentwise. Any other member is discrete and switches at DEFAULT_SWITCH_AT (on time). One value: a dotted address never reaches inside it.

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

switches: ClassVar[bool] = True#

True when the value (or part of it) switches on TIME rather than moving.

an.timing.kinds.QUATERNION_UNIT_TOLERANCE: float = 0.001#

How far a quaternion’s norm may be from 1 and still count as unit length.

class an.timing.kinds.QuaternionKind[source]#

Bases: FieldKind

A unit quaternion [x, y, z, w], interpolated by slerp after a sign fix.

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

an.timing.kinds.SLERP_LINEAR_THRESHOLD: float = 0.9995#

Above this |dot|, slerp falls back to normalised lerp (the arc is too short).

class an.timing.kinds.Segment(t, start, end)[source]#

Bases: object

Where an evaluation sits: time t inside the segment [start, end).

switched(switch_at)[source]#

Whether a discrete value has switched to the segment’s second key.

Return type:

bool

>>> Segment(0.5, 0.0, 1.0).switched(0.5), Segment(0.49, 0.0, 1.0).switched(0.5)
(True, False)
>>> Segment(0.9999999999, 0.0, 1.0).switched(1.0)  # only at b.time itself
False
class an.timing.kinds.VectorKind[source]#

Bases: FieldKind

A fixed-length list of numbers, interpolated componentwise.

check(value)[source]#

Why value is not valid for this kind, or None.

Return type:

str | None

interpolate(a, b, u, seg)[source]#

The value between keys a and b at eased progress u.

Return type:

Any

an.timing.kinds.kind_from_spec(spec)[source]#

A kind instance from its contract spec ({"kind": name, **params}), a bare kind name, or an instance (returned as is).

Return type:

FieldKind

>>> kind_from_spec("discrete")
DiscreteKind(switch_at=0.5)
>>> kind_from_spec({"kind": "points"})
Traceback (most recent call last):
 ...
an.timing.kinds.FieldKindError: unknown field kind 'points'; known: ['number', 'angle', 'vector', 'quaternion', 'color', 'orbit', 'discrete']
an.timing.kinds.kind_names(*, owner=None)[source]#

The registered kind names in registration order; only owner’s when given.

Return type:

tuple[str, ...]

an.timing.kinds.register_kind(name, factory, *, replace=False, owner=None)[source]#

Register a field kind under name (how a genre adds one).

owner names who registered it; only CORE_OWNER’s kinds reach an’s contract files.

Return type:

Callable[..., FieldKind]

>>> sorted(kind_names())[:3]
['angle', 'color', 'discrete']
an.timing.kinds.shortest_delta(a, b, period=360.0)[source]#

The signed shortest turn from a to b, in [-period/2, period/2].

An exact half turn goes in the direction of b - a, so a return trip retraces its path (0 -> 180 -> 0 goes there and back, not round).

Return type:

float

>>> shortest_delta(350, 10), shortest_delta(10, 350), shortest_delta(0, 180)
(20.0, -20.0, 180.0)
an.timing.kinds.slerp(a, b, u)[source]#

Spherical interpolation of unit quaternions [x, y, z, w], the short way.

Return type:

list[float]