an.engines#

Engines: seekable things the core drives frame by frame, and the renderer that drives them.

Core (ADR 0001 decision 12; core study §2.7). No engine is core: the core holds the PROTOCOL and the frame stage, and an engine – the 2D stage runtime, a previz view behind a page, a burns crop over a still – lives in its own package and registers the renderer frame_stage_renderer() builds from it.

  • an.engines.protocol – Engine (a factory that opens a session per shot), the time-driven and state-driven session protocols, the declared live tier, and describe(), which reads a session’s tier, drive mode and features off the members it implements (never off a flag).

  • an.engines.capture – the capture loop, sequential or batched, with the frame stage’s resolves (supersample, shutter).

  • an.engines.frame_stage – frame_stage_renderer(): an engine in, a Renderer out, using the core’s frame clock, capture loop, resolves and MP4 sink (an.media).

>>> from contextlib import contextmanager
>>> class Blank:
...     name = "blank"
...     @contextmanager
...     def open(self, job):
...         yield self
...     def frame(self, t): return b""
...     def state(self, t): return {}
>>> describe(Blank()).drive, frame_stage_renderer(Blank()).name
('time', 'blank')

Functions

capture_frames(session, requests, frames_dir, *)

Write one PNG per request into frames_dir, resolved to the declared size.

describe(session)

The tier, drive mode and features a session (or a session class) offers.

drive_mode(session)

"time", "state", or None for a session the frame stage cannot drive.

frame_stage_renderer(engine, *[, name, ...])

Turn engine into a Renderer: the core's clock, capture loop, resolves and sinks.

require_engine(engine)

Refuse an object that is not an engine, saying what is missing.

require_seekable(session)

The session's drive mode, or a refusal that says what to add.

requests_as_dicts(requests)

Requests as plain dicts, the shape a page or a subprocess takes.

shot_workspace(work_dir, shot_id)

<work_dir>/shot_<id>: the one per-shot scratch directory.

Classes

Engine(*args, **kwargs)

A stateless factory of loaded, seekable sessions -- one per shot render.

EngineProfile(tier, drive, features)

What a session can do, read off its members.

FrameJob(shot, ctx, workspace, frames_dir, ...)

What the frame stage asks of an engine for one shot.

FrameRequest(frame, times)

One output frame's instants, in the order they must be captured.

FrameStageRenderer(engine[, name, ...])

A Renderer that drives an Engine frame by frame.

LiveEngine(*args, **kwargs)

The live tier: apply, settle, capture in real time.

StateDriven(*args, **kwargs)

A session the core hands states to.

StateDrivenAdapter(session)

A state-driven session seen as a time-driven one: the core evaluates at(t).

TimeDriven(*args, **kwargs)

A session that evaluates the compiled channels itself.

Exceptions

FrameStageError

The frame stage could not produce a frame directory it can vouch for.

UnseekableEngineError

A session that the frame stage cannot drive; the message says what to add.

class an.engines.Engine(*args, **kwargs)[source]#

Bases: Protocol

A stateless factory of loaded, seekable sessions – one per shot render.

name: str#

The engine’s name ("stage"); also how provenance names it.

open(job)[source]#

Load job.shot and yield a session (see the module table).

Return type:

AbstractContextManager[Any]

class an.engines.EngineProfile(tier, drive, features)[source]#

Bases: object

What a session can do, read off its members.

class an.engines.FrameJob(shot, ctx, workspace, frames_dir, total_frames, supersample, frame_samples=None)[source]#

Bases: object

What the frame stage asks of an engine for one shot.

Built by the core, before Engine.open(): every knob here is already validated, so an engine never re-derives the frame count or the factor.

frame_samples: tuple[tuple[float, ...], ...] | None#

Per frame, the instants to capture and average, or None for one instant at i / fps.

requests()[source]#

Every frame’s request, frame i at i / fps unless a clock says otherwise.

Return type:

tuple[FrameRequest, ...]

>>> from types import SimpleNamespace
>>> job = FrameJob(None, SimpleNamespace(fps=4, resolution=(2, 2)), Path("."),
...                Path("."), total_frames=3, supersample=1)
>>> [r.times for r in job.requests()]
[(0.0,), (0.25,), (0.5,)]
property size: tuple[int, int]#

what every resolved frame must be.

Type:

The DECLARED (width, height)

supersample: int#

the engine draws at k times the declared size, and the core resolves back to it.

Type:

The validated supersample factor

workspace: Path#

The shot’s own scratch directory (<work_dir>/shot_<id>). The engine may stage files in it (the stage engine’s runtime copy); the core owns frames/ and the delivered mp4 inside it.

class an.engines.FrameRequest(frame, times)[source]#

Bases: object

One output frame’s instants, in the order they must be captured.

One instant unless a frame clock opened the shutter (RenderContext.frame_samples).

exception an.engines.FrameStageError[source]#

Bases: RuntimeError

The frame stage could not produce a frame directory it can vouch for.

A renderer built by frame_stage_renderer(..., error=...) re-raises it as its own typed error at its boundary.

class an.engines.FrameStageRenderer(engine, name='', supported_renderers=(), error=<class 'an.engines.capture.FrameStageError'>, capture_options=<factory>)[source]#

Bases: object

A Renderer that drives an Engine frame by frame.

Build it with frame_stage_renderer(). Stateless across renders, so one instance serves every shot of a parallel render: each render opens its own session.

capture_options: dict[str, Any]#

Capture-loop tunables passed through to an.engines.capture.capture_frames() (batch, workers, max_inflight, batch_pixels); unset = its defaults.

error#

alias of FrameStageError

probe_frames(shot, ctx, times)[source]#

The film’s frames of shot showing at each of times (seconds), as PNG bytes.

an probe (an#347): the session render() opens (the same engine, state-driven adapter, supersample and frame clock) and the same capture_frames(), asked only for the frames needed — so a probe frame IS the film’s frame, without the rest of the shot or the mux. Each instant is snapped to the frame showing then (floor(t * fps), clamped to the shot).

Return type:

list[bytes]

render(shot, ctx)[source]#

Render shot to mp4 through the engine; see the module docstring.

Return type:

RenderResult

supported_renderers: tuple[str, ...] = ()#

The Shot.renderer values this renderer claims (the ONE place it names them).

class an.engines.LiveEngine(*args, **kwargs)[source]#

Bases: Protocol

The live tier: apply, settle, capture in real time. Declared, not built.

Its recorder resamples to a constant rate (walkthru’s worked example); frame_stage_renderer does not drive it, and never falls back to it.

class an.engines.StateDriven(*args, **kwargs)[source]#

Bases: Protocol

A session the core hands states to.

render(state)[source]#

PNG bytes of state (supersample times the declared size).

Return type:

bytes

timeline: Any#

The an.timing Timeline the core evaluates.

class an.engines.StateDrivenAdapter(session)[source]#

Bases: object

A state-driven session seen as a time-driven one: the core evaluates at(t).

frame(t) is render(evaluate_timeline(timeline, t, space=space)) and state(t) is the evaluated state, so the capture loop and the conformance tests treat both drive modes alike. The session’s other members (resolve, provenance, …) are reached through attribute access.

Batched (an#286): frames(requests) evaluates every instant here and hands the session the STATES, through its optional render_states(states) (one round trip per batch, the member a JS-bridged view engine implements), else render per state. A session’s own frames is never forwarded: it would take times and bypass state.

Adapted by drive mode (an#286): project(point, t) and bounds(t) reach the session as project(point, state) and bounds(state) (the state-driven signature, previz’s project(point, {state, ...})), and exist only when the session has them, so describe() still reads capabilities from real members.

frames(requests)[source]#

One list of frames per request, a frame per instant, in order.

Return type:

list[list[bytes]]

class an.engines.TimeDriven(*args, **kwargs)[source]#

Bases: Protocol

A session that evaluates the compiled channels itself.

frame(t)[source]#

The scene at t as PNG bytes (supersample times the declared size).

Return type:

bytes

state(t)[source]#

The state the engine evaluated at t: a sparse pose, absent = at rest.

Return type:

Mapping[Any, Any]

exception an.engines.UnseekableEngineError[source]#

Bases: TypeError

A session that the frame stage cannot drive; the message says what to add.

an.engines.capture_frames(session, requests, frames_dir, *, factor=1, size=None, batch=None, workers=None, max_inflight=None, batch_pixels=None)[source]#

Write one PNG per request into frames_dir, resolved to the declared size.

session is time-driven (frame(t)), optionally batched (frames(requests)), optionally with its own resolve. A state-driven session is adapted by the frame stage before it reaches here. size, when known, sets the pixel budget and is passed to the resolve. None for any tunable reads the module default at call time.

Return type:

None

an.engines.describe(session)[source]#

The tier, drive mode and features a session (or a session class) offers.

Return type:

EngineProfile

an.engines.drive_mode(session)[source]#

"time", "state", or None for a session the frame stage cannot drive.

A session with both frame and render is time-driven: it evaluates its own document, and the core does not second-guess it.

Return type:

str | None

an.engines.frame_stage_renderer(engine, *, name=None, renderers=None, error=<class 'an.engines.capture.FrameStageError'>, capture_options=None)[source]#

Turn engine into a Renderer: the core’s clock, capture loop, resolves and sinks.

name defaults to the engine’s; renderers – the Shot.renderer values claimed – default to (name,). error is the typed exception the renderer raises at its boundary for every core error.

Return type:

FrameStageRenderer

an.engines.requests_as_dicts(requests)[source]#

Requests as plain dicts, the shape a page or a subprocess takes.

Return type:

list[dict[str, Any]]

an.engines.require_engine(engine)[source]#

Refuse an object that is not an engine, saying what is missing.

Return type:

None

>>> require_engine(object())
Traceback (most recent call last):
  ...
TypeError: an Engine needs `name` and `open(job)`; object lacks: name, open
an.engines.require_seekable(session)[source]#

The session’s drive mode, or a refusal that says what to add.

Return type:

str

>>> require_seekable(object())
Traceback (most recent call last):
  ...
an.engines.protocol.UnseekableEngineError: this engine session cannot be captured: it has no frame(t) (time-driven), and no render(state) with a timeline (state-driven). Add frame(t) and state(t) if the engine evaluates the timeline itself, or timeline and render(state) if the core should evaluate it.
an.engines.shot_workspace(work_dir, shot_id)[source]#

<work_dir>/shot_<id>: the one per-shot scratch directory.

Return type:

Path

Modules

capture

The capture loop: drive a session through every frame, resolve, write -- for ANY engine.

conformance

Conformance of a TIME-driven engine against the timing kernel's golden vectors.

frame_stage

frame_stage_renderer(engine): any seekable engine becomes a Renderer.

protocol

The Engine protocol: a seekable thing that turns a time, or a state, into a frame.