[FEATURE] Integrate with `marimo` notebook
Opened this issue · 3 comments
Terms
- Checked the existing issues and discussions to see if my issue had not already been reported;
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
Additional information
No response
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.
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 arender_html
method that returnsRevealJS
class instance, and add__repr_html__
toRevealJS
. Therender_html
could accept*args: list
(orargs: 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,{})
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