Skip to content

View provider ​

manim-marimo registers one marimo-studio view provider under the key manim-marimo/manim, through the marimo_studio.view_provider entry point. Studio discovers it in the environment of the marimo server when Studio starts.

Starter ​

Starter IDTitleFiles
manim-marimo/manim:defaultManim animationscene.py, index.html, style.css, player.js, AGENTS.md

index.html takes the view's title as its heading and lists the notebook's output cells as <marimo-cell> hosts beside the player. Studio's New view dialog shows the starter as a Video with the manim-marimo mark and a preview of the page. While Studio finds no worker, the dialog shows that the starter needs manim-marimo[render], as Availability describes.

Options ​

toml
schema = 1
provider = "manim-marimo/manim"

[options]
quality = "preview"
OptionValuesDefault
qualitydraft, preview, productionpreview in Studio Preview, production in run mode and export

Project files ​

PathRole
index.htmlThe page. Published.
*.css, *.js, assets/**Page assets. Published.
*.pyScenes and helper modules. Private to the render.
Other filesPrivate to the render, such as data a scene loads

Studio's Source panel edits the project's HTML, CSS, JavaScript, Python, JSON, Markdown, TOML, SVG, and YAML files, such as states.yaml, with syntax highlighting.

Renders ​

Each <marimo-output render="path.py"> host on index.html declares a render of that scene file. A render reads the targets of every literal value(), output(), and cell() call in the scene file and in the project modules it imports. output() and cell() accept image/png, image/jpeg, and image/webp.

When an output() call passes height as a number, such as output("chart", height=2.6), the build reports that height to Studio in points, about 99 points per scene unit. Studio then draws a matplotlib figure or a Vega-Lite chart at that height with the notebook's aspect ratio, so its text keeps its point size in the scene: a 10 point tick label matches Text(font_size=10). Without a literal height, the figure keeps the size the notebook gives it.

FileMedia typeRole
animation.mp4video/mp4Main file, downloaded
poster.pngimage/pngPreview, the last frame
metadata.jsonapplication/jsonduration, fps, width, height

Each render runs in a worker process under Studio's command deadline, started from the command Availability describes. The build runs every declared scene once with all reads at their defaults, skipping animation and encoding.

Traced shapes ​

A render traces each shape the scene's code creates to the notebook results it shows, and returns the shapes as Studio render regions. A shape is a region of animation.mp4 for each run of frames in which it keeps its box and reads, and the shapes of the last frame are regions of poster.png. With Lens installed, Studio's preview makes each region a selection target that follows the shape as the movie plays.

Scene codeRegion reads
joint = np.asarray(value("joint")) then Rectangle(fill_opacity=joint[i, j])joint[2][3] for that rectangle
for index, p in enumerate(probabilities) then bar.become(target) built from pprobabilities[4] once the bar animates
for i, (h, c) in enumerate(zip(heights, colors)) then Square(color=c).set_height(h)heights[1] and colors[1] for that square
An updater that moves a dot to path[index]path[index] for each frame range
output("figure")figure
bar = make(heights[0]) with a helper that returns a Squareheights[0] for that square
label = Text(names[0]).next_to(dot, UP)names[0] for the label, none for the dot
for index in range(len(probabilities)) then a label at indexNone

Each region names the shape's variable and class, such as bar: Rectangle, and the line that created it. A statement gives its reads to the shapes it creates and to the shapes it calls methods on, such as dot in dot.animate.move_to(...). A shape it only passes along, as an anchor or to an animation, keeps its own reads. A shape shows the reads of the play() call that moves it while that call writes frames. A shape that shows no read, such as a title or an axis label, has no region, and selecting it selects the whole movie.

The tracer evaluates index expressions made of names, integers, and arithmetic, and keeps a shorter selector, such as chain, when an index depends on anything else. A name that holds part of a value() read must still hold the notebook's value of that part, so a loop name reused for another read, or an index that moved on after the loop, drops the read.

Studio keeps 4,096 regions per render. When a scene's traced shapes need more, adjacent regions of a shape merge into one over both time spans, the regions of large shapes first. A shape that holds still for about half a second keeps its exact box until every moving region has merged, so a scene that pauses on a moment gives every shape there an exact target.

Tracing watches only project code and records the boxes of shapes at most twelve times a second: every third frame at 30 frames per second, and every other frame at 24. A statement that changes no shape stops being watched after its first run.

Diagnostics ​

CodeSeverityRaised byMeaning
manim-input-missingerrorInspectionindex.html is missing
manim-scene-missingerrorInspectionA render host names a scene file that does not exist
manim-source-invaliderrorInspectionA Python file in the project does not parse
manim-read-dynamicwarningInspectionA read's target is not a string literal
manim-options-invaliderrorBuildquality is not one of the listed values
manim-render-failederrorBuild, renderThe scene raised. The message names the exception, and the location points at the deepest frame in the project.

Availability ​

Scenes render with the manim-marimo-worker command, which the render extra installs. Studio looks for it in the notebook's pixi environment first, when the notebook declares [tool.pixi] metadata and runs with --sandbox=pixi or a marimo-studio command targets it, and then on PATH. Without the command, scenes render in the Python that runs Studio.

The provider reports itself available when the worker's ManimGL imports and imageio-ffmpeg locates an FFmpeg binary. The worker and Studio's provider read and write the same render jobs, so both install one manim-marimo release. Otherwise the starter is unavailable with a reason that names both versions. marimo-studio starters --target analysis.py shows the availability for one notebook:

text
available 0.0.1 manim-marimo/manim:default
  A Manim scene, a video player, and native notebook context.
  provider manim-marimo/manim