jeertmans / manim-slides

Tool for live presentations using manim
https://manim-slides.eertmans.be
MIT License
483 stars 49 forks source link

[FEATURE] Integrate with `marimo` notebook #490

Open way-zer opened 3 days ago

way-zer commented 3 days ago

Terms

Description

Marimo is an open-source reactive notebook for Python — reproducible, git-friendly, executable as a script, and shareable as an app. As an alternative to jupyter/ipython.

There is ipython_magic, but not work for marimo(need invoke and ipython).So I make a monkey-patch, but It's better to add support natively.

The monkey-patch add Slide._repr_html_ like ipython_magic. Document about this api

Monkey patch

```python from manim_slides import Slide import logging import mimetypes from pathlib import Path from manim import config, logger from manim.constants import RendererType from manim.renderer.shader import shader_program_cache from manim_slides.convert import ( RevealJS, Template, file_to_data_uri, get_duration_ms, os, ) from manim_slides.present import get_scenes_presentation_config class PatchedRevealJS(RevealJS): def convert(self) -> str: if self.data_uri: assets_dir = Path("") # Actually we won't care. else: raise NotImplementedError() revealjs_template = Template(self.load_template()) options = self.model_dump() options["assets_dir"] = assets_dir has_notes = any( slide_config.notes != "" for presentation_config in self.presentation_configs for slide_config in presentation_config.slides ) return revealjs_template.render( file_to_data_uri=file_to_data_uri, get_duration_ms=get_duration_ms, has_notes=has_notes, env=os.environ, **options, ) def _extend_mime(cls): logging.getLogger("manim-slides").setLevel(logging.getLogger("manim").level) renderer = None if config.renderer == RendererType.OPENGL: from manim.renderer.opengl_renderer import OpenGLRenderer renderer = OpenGLRenderer() try: scene = cls(renderer=renderer) scene.render() finally: # Shader cache becomes invalid as the context is destroyed shader_program_cache.clear() # Close OpenGL window here instead of waiting for the main thread to # finish causing the window to stay open and freeze if renderer is not None and renderer.window is not None: renderer.window.close() if config["output_file"] is None: logger.info("No output file produced") return file_type = mimetypes.guess_type(config["output_file"])[0] or "video/mp4" if not file_type.startswith("video"): raise ValueError(f"Manim Slides only supports video files, not {file_type}") presentation_configs = get_scenes_presentation_config( [cls.__name__], Path("./slides") ) out_data = PatchedRevealJS( presentation_configs=presentation_configs, data_uri=True ).convert() return """

""".format( srcdoc=out_data.replace('"', "'") ) Slide._repr_html_ = classmethod(_extend_mime) ```

Usage:

from manim import Scene, Square, Circle, BLUE, GREEN, PINK, Create, Transform
from manim_slides.slide import Slide

import helper

blue_circle = Circle(color=BLUE, fill_opacity=0.5)
green_square = Square(color=GREEN, fill_opacity=0.8)
class CircleToSquare(Slide):
    def construct(self):
        self.play(Create(blue_circle))
        self.next_slide()

        self.play(Transform(blue_circle, green_square))

CircleToSquare

Screenshots

image

Additional information

No response

jeertmans commented 3 days ago

Hello @way-zer, this looks very interesting!

I have a small question: how do you pass rendering arguments?

Maybe it would be great if Slide had a render_html method that returns RevealJS class instance, and add __repr_html__ to RevealJS. The render_html could accept *args: list (or args: Sequence[str]) and use them to control the rendering.

way-zer commented 3 days ago

Hello @way-zer, this looks very interesting!

I have a small question: how do you pass rendering arguments?

Maybe it would be great if Slide had a render_html method that returns RevealJS class instance, and add __repr_html__ to RevealJS. The render_html could accept *args: list (or args: Sequence[str]) and use them to control the rendering.

Good question, This is what I thought before, but it doesn't work.

# not work because render is delayed
with tempconfig({"frame_height": 100.0}):
    CircleToSquare

I plan to discuss with marimo that it should provide a way to render with contextmanager.

Maybe, wrapper with config, and config is optional.

from manim import tempconfig
class withConfig:
    def __init__(self, s, cfg):
        self.s = s
        self.cfg = cfg
    def _repr_html_(self):
        with tempconfig(self.cfg):
            return self.s._repr_html_()

withConfig(CircleToSquare,{})
jeertmans commented 3 days ago

This new feature doesn't seem to be unique to marimo, and will likely work on Jupyter Notebook too. I think adding new methods to both Slide and RevealJS can be good enough.

Something like this:

class Slide:
    def render_html(args: list[str]) -> RevealJS:
        # render slides with optional arguments and return RevealJS class instance

    def _repr_html_(self) -> HTML:
        self.render_html([])._repr_html_()

class RevealJS:
    def _repr_html_(self) -> HTML:
        # convert to HTML and generate output cell