Box mini-project#

[1]:
from manim import *
config.media_embed = True; config.media_width = "100%"
_RV = "-v WARNING -qm --progress_bar None --disable_caching Example"
_RI = "-v WARNING -s --progress_bar None --disable_caching Example"
Manim Community v0.15.1

Target#

Let’s create the following animation, we will create the Box object and define two animations OpenBox and CloseBox, which will only work for this particular VMobject.

b1

Box definition#

[2]:
ANGLE_OPEN_BOX = PI * 2.3 / 2

class Box(VMobject):
    def __init__(self,
                 width=6,
                 height=3,
                 cover_buff=None,
                 fill_opacity=0.5,
                 fill_color="#cdab7e",
                 stroke_color="#D2B48C",
                 open=False,
                 **kwargs):
        super().__init__(fill_opacity=fill_opacity,
                         fill_color=fill_color,
                         stroke_color=stroke_color,
                  **kwargs)
        if cover_buff is None:
            cover_buff = width * 0.8 / 2
        else:
            assert (cover_buff < width/2), "cover_buff >= width/2"
        main_rect = Square(width,color=RED)
        # 0.25 is Up right corner
        # 1 is Up left corner, remember vmob.point_from_proportion(alpha)
        self.base_box = main_rect.get_subcurve(0.25,1).match_style(self)
        self.base_box.stretch_to_fit_height(height)
        self._open = open
        self.new_stroke = self.base_box.get_stroke_width() * 4
        self.left_cover = self.get_cover(RIGHT,cover_buff)
        self.right_cover  = self.get_cover(LEFT,cover_buff)
        covers = VGroup(self.left_cover,self.right_cover).match_color(self)
        self.add(self.base_box, *covers)

    def get_cover(self, side, cover_buff):
        pivot_index = 0 if side[0] == 1 else -1
        pivot = self.base_box.get_all_points()[pivot_index]
        cover = Line(pivot, pivot + side * cover_buff, stroke_width=self.new_stroke)
        if self._open:
            cover.rotate(ANGLE_OPEN_BOX*side[0],about_point=pivot)
        return cover

Animations#

[ ]:
class OpenBox(Animation):
    def __init__(self, box, open=True, run_time=3, **kwargs):
        # Assert open/close
        _open = "open" if open else "close"
        assert (box._open != open), f"box is already {_open}"
        # Define sign
        self._sign = 1 if box._open else -1
        super().__init__(box, run_time=run_time, **kwargs)

    def interpolate_mobject(self, alpha):
        self.mobject.become(self.starting_mobject)
        self.rotate_cover(self.mobject.right_cover, alpha)
        self.rotate_cover(self.mobject.left_cover, alpha, -1)

    def rotate_cover(self, cover, alpha, sign=1):
        cover.rotate(
            sign * self._sign * self.rate_func(alpha) * ANGLE_OPEN_BOX,
            about_point=cover.get_start(),
            about_edge=cover.get_start(),
        )

    def clean_up_from_scene(self, scene):
        # change open to close or close to open at the end
        self.mobject._open = not self.mobject._open
        super().clean_up_from_scene(scene)

When creating animations or objects that are going to be reused, it is a good idea that we do unnecessary redefinitions.

[4]:
class CloseBox(OpenBox):
    def __init__(self, box, open=False, **kwargs):
        super().__init__(box, open, **kwargs)
[5]:
class Example(Scene):
    def construct(self):
        b = Box()
        self.play(DrawBorderThenFill(b))
        self.play(OpenBox(b))
        self.play(CloseBox(b))
        self.play(OpenBox(b))
        self.play(CloseBox(b))
        self.wait()

%manim $_RV

Assert test#

If the box is open from the beginning then it should give us an error:

[6]:
class Example(Scene):
    def construct(self):
        b = Box(open=True)
        self.add(b)
        self.play(OpenBox(b))
        self.wait()

%manim $_RV
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Input In [6], in <cell line: 8>()
      5         self.play(OpenBox(b))
      6         self.wait()
----> 8 get_ipython().run_line_magic('manim', '$_RV')

File ~/GitHubProjects/mce-inter/docs/venv/lib/python3.9/site-packages/IPython/core/interactiveshell.py:2294, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2292     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2293 with self.builtin_trap:
-> 2294     result = fn(*args, **kwargs)
   2295 return result

File ~/GitHubProjects/mce-inter/docs/venv/lib/python3.9/site-packages/manim/utils/ipython_magic.py:148, in ManimMagic.manim(self, line, cell, local_ns)
    146     SceneClass = local_ns[config["scene_names"][0]]
    147     scene = SceneClass(renderer=renderer)
--> 148     scene.render()
    149 finally:
    150     # Shader cache becomes invalid as the context is destroyed
    151     shader_program_cache.clear()

File ~/GitHubProjects/mce-inter/docs/venv/lib/python3.9/site-packages/manim/scene/scene.py:222, in Scene.render(self, preview)
    220 self.setup()
    221 try:
--> 222     self.construct()
    223 except EndSceneEarlyException:
    224     pass

Input In [6], in Example.construct(self)
      3 b = Box(open=True)
      4 self.add(b)
----> 5 self.play(OpenBox(b))
      6 self.wait()

Input In [3], in OpenBox.__init__(self, box, open, run_time, **kwargs)
      2 def __init__(self, box, open=True, run_time=3, **kwargs):
      3     # Assert open/close
      4     _open = "open" if open else "close"
----> 5     assert (box._open != open), f"box is already {_open}"
      6     # Define sign
      7     self._sign = 1 if box._open else -1

AssertionError: box is already open

It works, using assert is a good idea when we need our animations to have certain structures.