Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Integrate with marimo notebook #490

Open
1 task done
way-zer opened this issue Nov 13, 2024 · 3 comments
Open
1 task done

[FEATURE] Integrate with marimo notebook #490

way-zer opened this issue Nov 13, 2024 · 3 comments
Labels
enhancement New feature or request lib Related to the library (a.k.a. module)
Milestone

Comments

@way-zer
Copy link

way-zer commented Nov 13, 2024

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

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 """<div style="position:relative;padding-bottom:56.25%;"><iframe style="width:100%;height:100%;position:absolute;left:0px;top:0px;" frameborder="0" width="100%" height="100%" allowfullscreen allow="autoplay" srcdoc="{srcdoc}"></iframe></div>""".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

@way-zer way-zer added the enhancement New feature or request label Nov 13, 2024
@jeertmans
Copy link
Owner

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.

@jeertmans jeertmans added the lib Related to the library (a.k.a. module) label Nov 13, 2024
@way-zer
Copy link
Author

way-zer commented Nov 13, 2024

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
Copy link
Owner

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

@jeertmans jeertmans added this to the v6 milestone Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request lib Related to the library (a.k.a. module)
Projects
None yet
Development

No branches or pull requests

2 participants