From d2a50b9f039b91586317a95f2258ca8f9bd1d25d Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 25 Dec 2021 00:18:31 +0100 Subject: [PATCH 1/9] Replace finish with clean_up_from_scene in ShowPassingFlash --- manim/animation/indication.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manim/animation/indication.py b/manim/animation/indication.py index fc2d4de2e6..0eb95f78ff 100644 --- a/manim/animation/indication.py +++ b/manim/animation/indication.py @@ -310,8 +310,8 @@ def _get_bounds(self, alpha: float) -> Tuple[float]: lower = max(lower, 0) return (lower, upper) - def finish(self) -> None: - super().finish() + def clean_up_from_scene(self, scene: "Scene") -> None: + super().clean_up_from_scene(scene) for submob, start in self.get_all_families_zipped(): submob.pointwise_become_partial(start, 0, 1) From 5ed918d915ddba1b1ace94680b6674c12c60e61b Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 25 Dec 2021 01:31:27 +0100 Subject: [PATCH 2/9] Add introducer flag to animations --- manim/animation/animation.py | 26 ++++++++++++++++++++++++++ manim/animation/composition.py | 21 +++++++++++++++++++++ manim/animation/indication.py | 2 +- manim/scene/scene.py | 3 +++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/manim/animation/animation.py b/manim/animation/animation.py index 3f2c4d35fd..b72c5e769a 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -134,6 +134,7 @@ def __init__( name: str = None, remover: bool = False, # remove a mobject from the screen? suspend_mobject_updating: bool = True, + introducer: bool = False, **kwargs, ) -> None: self._typecheck_input(mobject) @@ -141,6 +142,7 @@ def __init__( self.rate_func: Callable[[float], float] = rate_func self.name: str | None = name self.remover: bool = remover + self.introducer: bool = introducer self.suspend_mobject_updating: bool = suspend_mobject_updating self.lag_ratio: float = lag_ratio if config["renderer"] == "opengl": @@ -222,6 +224,20 @@ def clean_up_from_scene(self, scene: Scene) -> None: if self.is_remover(): scene.remove(self.mobject) + def setup_scene(self, scene: "Scene") -> None: + """Setup up the :class:`~.Scene` before starting the animation. + + This includes to :meth:`~.Scene.add` the Animation's + :class:`~.Mobject` if the animation is an introducer. + + Parameters + ---------- + scene + The scene the animation should be cleaned up from. + """ + if self.is_introducer(): + scene.add(self.mobject) + def create_starting_mobject(self) -> Mobject: # Keep track of where the mobject starts return self.mobject.copy() @@ -436,6 +452,16 @@ def is_remover(self) -> bool: """ return self.remover + def is_introducer(self) -> bool: + """Test if a the animation is a remover. + + Returns + ------- + bool + ``True`` if the animation is a remover, ``False`` otherwise. + """ + return self.introducer + def prepare_animation( anim: Animation | mobject._AnimationBuilder, diff --git a/manim/animation/composition.py b/manim/animation/composition.py index d68b0e6ce8..28387e35b9 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -57,6 +57,18 @@ def begin(self) -> None: for anim in self.animations: anim.begin() + def setup_scene(self, scene) -> None: + if self.is_introducer(): + for anim in self.animations: + if not anim.is_introducer() and anim.mobject is not None: + scene.add(anim.mobject) + + for anim in self.animations: + anim.setup_scene(scene) + + def is_introducer(self) -> bool: + return any(anim.is_introducer() for anim in self.animations) + def finish(self) -> None: for anim in self.animations: anim.finish() @@ -127,6 +139,14 @@ def update_mobjects(self, dt: float) -> None: if self.active_animation: self.active_animation.update_mobjects(dt) + def setup_scene(self, scene) -> None: + if self.is_introducer(): + for anim in self.animations: + if not anim.is_introducer() and anim.mobject is not None: + scene.add(anim.mobject) + + self.scene = scene + def update_active_animation(self, index: int) -> None: self.active_index = index if index >= len(self.animations): @@ -135,6 +155,7 @@ def update_active_animation(self, index: int) -> None: self.active_end_time: float | None = None else: self.active_animation = self.animations[index] + self.active_animation.setup_scene(self.scene) self.active_animation.begin() self.active_start_time = self.anims_with_timings[index][1] self.active_end_time = self.anims_with_timings[index][2] diff --git a/manim/animation/indication.py b/manim/animation/indication.py index 0eb95f78ff..1eeb5ed8f6 100644 --- a/manim/animation/indication.py +++ b/manim/animation/indication.py @@ -300,7 +300,7 @@ def construct(self): def __init__(self, mobject: "VMobject", time_width: float = 0.1, **kwargs) -> None: self.time_width = time_width - super().__init__(mobject, remover=True, **kwargs) + super().__init__(mobject, remover=True, introducer=True, **kwargs) def _get_bounds(self, alpha: float) -> Tuple[float]: tw = self.time_width diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 26be794b92..b8dc700e6c 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -446,6 +446,8 @@ def add(self, *mobjects): def add_mobjects_from_animations(self, animations): curr_mobjects = self.get_mobject_family_members() for animation in animations: + if animation.is_introducer(): + continue # Anything animated that's not already in the # scene gets added to the scene mob = animation.mobject @@ -1022,6 +1024,7 @@ def compile_animation_data(self, *animations: Animation, **play_kwargs): def begin_animations(self) -> None: """Start the animations of the scene.""" for animation in self.animations: + animation.setup_scene(self) animation.begin() def is_current_animation_frozen_frame(self) -> bool: From b3ad8e7d5d24332e12ae954f0bf4d90a5dbf30e8 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 25 Dec 2021 03:00:59 +0100 Subject: [PATCH 3/9] Mark some animations as introducers --- manim/animation/creation.py | 36 +++++++++++++++++++++++++++++------- manim/animation/fading.py | 3 +++ manim/animation/growing.py | 2 +- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/manim/animation/creation.py b/manim/animation/creation.py index 61d21f2773..cd9640a1fc 100644 --- a/manim/animation/creation.py +++ b/manim/animation/creation.py @@ -115,11 +115,15 @@ class ShowPartial(Animation): """ def __init__( - self, mobject: VMobject | OpenGLVMobject | OpenGLSurface | None, **kwargs + self, + mobject: VMobject | OpenGLVMobject | OpenGLSurface | None, + **kwargs, ): pointwise = getattr(mobject, "pointwise_become_partial", None) if not callable(pointwise): - raise NotImplementedError("This animation is not defined for this Mobject.") + raise NotImplementedError( + "This animation is not defined for this Mobject." + ) super().__init__(mobject, **kwargs) def interpolate_submobject( @@ -167,9 +171,12 @@ def __init__( self, mobject: VMobject | OpenGLVMobject | OpenGLSurface, lag_ratio: float = 1.0, + introducer: bool = True, **kwargs, ) -> None: - super().__init__(mobject, lag_ratio=lag_ratio, **kwargs) + super().__init__( + mobject, lag_ratio=lag_ratio, introducer=introducer, **kwargs + ) def _get_bounds(self, alpha: float) -> tuple[int, float]: return (0, alpha) @@ -199,7 +206,9 @@ def __init__( remover: bool = True, **kwargs, ) -> None: - super().__init__(mobject, rate_func=rate_func, remover=remover, **kwargs) + super().__init__( + mobject, rate_func=rate_func, remover=remover, **kwargs + ) class DrawBorderThenFill(Animation): @@ -223,10 +232,17 @@ def __init__( stroke_color: str = None, draw_border_animation_config: dict = {}, # what does this dict accept? fill_animation_config: dict = {}, + introducer: bool = True, **kwargs, ) -> None: self._typecheck_input(vmobject) - super().__init__(vmobject, run_time=run_time, rate_func=rate_func, **kwargs) + super().__init__( + vmobject, + run_time=run_time, + introducer=introducer, + rate_func=rate_func, + **kwargs, + ) self.stroke_width = stroke_width self.stroke_color = stroke_color self.draw_border_animation_config = draw_border_animation_config @@ -235,7 +251,9 @@ def __init__( def _typecheck_input(self, vmobject: VMobject | OpenGLVMobject) -> None: if not isinstance(vmobject, (VMobject, OpenGLVMobject)): - raise TypeError("DrawBorderThenFill only works for vectorized Mobjects") + raise TypeError( + "DrawBorderThenFill only works for vectorized Mobjects" + ) def begin(self) -> None: self.outline = self.get_outline() @@ -245,7 +263,9 @@ def get_outline(self) -> Mobject: outline = self.mobject.copy() outline.set_fill(opacity=0) for sm in outline.family_members_with_points(): - sm.set_stroke(color=self.get_stroke_color(sm), width=self.stroke_width) + sm.set_stroke( + color=self.get_stroke_color(sm), width=self.stroke_width + ) return outline def get_stroke_color(self, vmobject: VMobject | OpenGLVMobject) -> Color: @@ -313,6 +333,8 @@ def __init__( rate_func=rate_func, run_time=run_time, lag_ratio=lag_ratio, + introducer=not reverse, + remover=reverse, **kwargs, ) diff --git a/manim/animation/fading.py b/manim/animation/fading.py index 8ee3d551bf..6aaba7bf5e 100644 --- a/manim/animation/fading.py +++ b/manim/animation/fading.py @@ -137,6 +137,9 @@ def construct(self): """ + def __init__(self, *mobjects: Mobject, **kwargs) -> None: + super().__init__(*mobjects, introducer=True, **kwargs) + def create_target(self): return self.mobject diff --git a/manim/animation/growing.py b/manim/animation/growing.py index 137d0c21b7..4011eb02b2 100644 --- a/manim/animation/growing.py +++ b/manim/animation/growing.py @@ -79,7 +79,7 @@ def __init__( ) -> None: self.point = point self.point_color = point_color - super().__init__(mobject, **kwargs) + super().__init__(mobject, introducer=True, **kwargs) def create_target(self) -> Mobject: return self.mobject From de73b2438126c7df42d9bb627fd0fdfdb540e2c8 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 25 Dec 2021 15:06:05 +0100 Subject: [PATCH 4/9] Handle animating graph --- manim/animation/animation.py | 6 + manim/animation/composition.py | 14 +-- manim/animation/creation.py | 9 +- manim/mobject/graph.py | 211 ++++++++++++++++++++++----------- manim/scene/scene.py | 2 +- 5 files changed, 162 insertions(+), 80 deletions(-) diff --git a/manim/animation/animation.py b/manim/animation/animation.py index b72c5e769a..2092add785 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -135,6 +135,8 @@ def __init__( remover: bool = False, # remove a mobject from the screen? suspend_mobject_updating: bool = True, introducer: bool = False, + *, + _on_finish: Callable[[], None] = lambda _: None, **kwargs, ) -> None: self._typecheck_input(mobject) @@ -145,6 +147,7 @@ def __init__( self.introducer: bool = introducer self.suspend_mobject_updating: bool = suspend_mobject_updating self.lag_ratio: float = lag_ratio + self._on_finish: Callable[["Scene"], None] = _on_finish if config["renderer"] == "opengl": self.starting_mobject: OpenGLMobject = OpenGLMobject() self.mobject: OpenGLMobject = ( @@ -221,6 +224,7 @@ def clean_up_from_scene(self, scene: Scene) -> None: scene The scene the animation should be cleaned up from. """ + self._on_finish(scene) if self.is_remover(): scene.remove(self.mobject) @@ -235,6 +239,8 @@ def setup_scene(self, scene: "Scene") -> None: scene The scene the animation should be cleaned up from. """ + if scene is None: + return if self.is_introducer(): scene.add(self.mobject) diff --git a/manim/animation/composition.py b/manim/animation/composition.py index 28387e35b9..2084eb88b2 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -39,7 +39,7 @@ def __init__( self.group = group if self.group is None: mobjects = remove_list_redundancies( - [anim.mobject for anim in self.animations], + [anim.mobject for anim in self.animations if not anim.is_introducer()], ) if config["renderer"] == "opengl": self.group = OpenGLGroup(*mobjects) @@ -58,17 +58,9 @@ def begin(self) -> None: anim.begin() def setup_scene(self, scene) -> None: - if self.is_introducer(): - for anim in self.animations: - if not anim.is_introducer() and anim.mobject is not None: - scene.add(anim.mobject) - for anim in self.animations: anim.setup_scene(scene) - def is_introducer(self) -> bool: - return any(anim.is_introducer() for anim in self.animations) - def finish(self) -> None: for anim in self.animations: anim.finish() @@ -76,6 +68,7 @@ def finish(self) -> None: self.group.resume_updating() def clean_up_from_scene(self, scene: Scene) -> None: + self._on_finish(scene) for anim in self.animations: if self.remover: anim.remover = self.remover @@ -126,6 +119,7 @@ def interpolate(self, alpha: float) -> None: class Succession(AnimationGroup): def __init__(self, *animations: Animation, lag_ratio: float = 1, **kwargs) -> None: super().__init__(*animations, lag_ratio=lag_ratio, **kwargs) + self.scene = None def begin(self) -> None: assert len(self.animations) > 0 @@ -140,6 +134,8 @@ def update_mobjects(self, dt: float) -> None: self.active_animation.update_mobjects(dt) def setup_scene(self, scene) -> None: + if scene is None: + return if self.is_introducer(): for anim in self.animations: if not anim.is_introducer() and anim.mobject is not None: diff --git a/manim/animation/creation.py b/manim/animation/creation.py index cd9640a1fc..ee701ba678 100644 --- a/manim/animation/creation.py +++ b/manim/animation/creation.py @@ -207,7 +207,11 @@ def __init__( **kwargs, ) -> None: super().__init__( - mobject, rate_func=rate_func, remover=remover, **kwargs + mobject, + rate_func=rate_func, + introducer=False, + remover=remover, + **kwargs, ) @@ -328,13 +332,14 @@ def __init__( lag_ratio, ) self.reverse = reverse + if "remover" not in kwargs: + kwargs["remover"] = reverse super().__init__( vmobject, rate_func=rate_func, run_time=run_time, lag_ratio=lag_ratio, introducer=not reverse, - remover=reverse, **kwargs, ) diff --git a/manim/mobject/graph.py b/manim/mobject/graph.py index a9f803b86f..e0079b62b2 100644 --- a/manim/mobject/graph.py +++ b/manim/mobject/graph.py @@ -7,7 +7,7 @@ ] from copy import copy -from typing import Hashable, List, Optional, Tuple, Type, Union +from typing import Hashable, Iterable, List, Optional, Tuple, Type, Union import networkx as nx import numpy as np @@ -499,43 +499,16 @@ def __getitem__(self: Graph, v: Hashable) -> Mobject: def __repr__(self: Graph) -> str: return f"Graph on {len(self.vertices)} vertices and {len(self.edges)} edges" - def _add_vertex( + def _create_vertex( self, vertex: Hashable, - position: np.ndarray | None = None, + position: Optional[np.ndarray] = None, label: bool = False, label_fill_color: str = BLACK, - vertex_type: type[Mobject] = Dot, - vertex_config: dict | None = None, - vertex_mobject: dict | None = None, - ) -> Mobject: - """Add a vertex to the graph. - - Parameters - ---------- - - vertex - A hashable vertex identifier. - position - The coordinates where the new vertex should be added. If ``None``, the center - of the graph is used. - label - Controls whether or not the vertex is labeled. If ``False`` (the default), - the vertex is not labeled; if ``True`` it is labeled using its - names (as specified in ``vertex``) via :class:`~.MathTex`. Alternatively, - any :class:`~.Mobject` can be passed to be used as the label. - label_fill_color - Sets the fill color of the default labels generated when ``labels`` - is set to ``True``. Has no effect for other values of ``label``. - vertex_type - The mobject class used for displaying vertices in the scene. - vertex_config - A dictionary containing keyword arguments to be passed to - the class specified via ``vertex_type``. - vertex_mobject - The mobject to be used as the vertex. Overrides all other - vertex customization options. - """ + vertex_type: Type["Mobject"] = Dot, + vertex_config: Optional[dict] = None, + vertex_mobject: Optional[dict] = None, + ) -> Tuple[Hashable, np.ndarray, dict, "Mobject"]: if position is None: position = self.get_center() @@ -547,73 +520,116 @@ def _add_vertex( f"Vertex identifier '{vertex}' is already used for a vertex in this graph.", ) - self._graph.add_node(vertex) - self._layout[vertex] = position - if isinstance(label, (Mobject, OpenGLMobject)): - self._labels[vertex] = label + label = label elif label is True: - self._labels[vertex] = MathTex(vertex, fill_color=label_fill_color) + label = MathTex(vertex, fill_color=label_fill_color) + elif vertex in self._labels: + label = self._labels[vertex] + else: + label = None base_vertex_config = copy(self.default_vertex_config) base_vertex_config.update(vertex_config) vertex_config = base_vertex_config - if vertex in self._labels: - vertex_config["label"] = self._labels[vertex] + if label is not None: + vertex_config["label"] = label if vertex_type is Dot: vertex_type = LabeledDot - self._vertex_config[vertex] = vertex_config - if vertex_mobject is None: - self.vertices[vertex] = vertex_type(**vertex_config) - else: - self.vertices[vertex] = vertex_mobject + vertex_mobject = vertex_type(**vertex_config) + + vertex_mobject.move_to(position) + + return (vertex, position, vertex_config, vertex_mobject) + + def _add_created_vertex( + self, + vertex: Hashable, + position: np.ndarray, + vertex_config: dict, + vertex_mobject: "Mobject", + ) -> "Mobject": + if vertex in self.vertices: + raise ValueError( + f"Vertex identifier '{vertex}' is already used for a vertex in this graph.", + ) + + self._graph.add_node(vertex) + self._layout[vertex] = position + + if "label" in vertex_config: + self._labels[vertex] = vertex_config["label"] + + self._vertex_config[vertex] = vertex_config + self.vertices[vertex] = vertex_mobject self.vertices[vertex].move_to(position) self.add(self.vertices[vertex]) return self.vertices[vertex] - def add_vertices( - self: Graph, - *vertices: Hashable, - positions: dict | None = None, - labels: bool = False, + def _add_vertex( + self, + vertex: Hashable, + position: np.ndarray | None = None, + label: bool = False, label_fill_color: str = BLACK, vertex_type: type[Mobject] = Dot, vertex_config: dict | None = None, - vertex_mobjects: dict | None = None, - ): - """Add a list of vertices to the graph. + vertex_mobject: dict | None = None, + ) -> Mobject: + """Add a vertex to the graph. Parameters ---------- - vertices - Hashable vertex identifiers. - positions - A dictionary specifying the coordinates where the new vertices should be added. - If ``None``, all vertices are created at the center of the graph. - labels + vertex + A hashable vertex identifier. + position + The coordinates where the new vertex should be added. If ``None``, the center + of the graph is used. + label Controls whether or not the vertex is labeled. If ``False`` (the default), the vertex is not labeled; if ``True`` it is labeled using its names (as specified in ``vertex``) via :class:`~.MathTex`. Alternatively, any :class:`~.Mobject` can be passed to be used as the label. label_fill_color Sets the fill color of the default labels generated when ``labels`` - is set to ``True``. Has no effect for other values of ``labels``. + is set to ``True``. Has no effect for other values of ``label``. vertex_type The mobject class used for displaying vertices in the scene. vertex_config A dictionary containing keyword arguments to be passed to the class specified via ``vertex_type``. - vertex_mobjects - A dictionary whose keys are the vertex identifiers, and whose - values are mobjects that should be used as vertices. Overrides - all other vertex customization options. + vertex_mobject + The mobject to be used as the vertex. Overrides all other + vertex customization options. """ + return self._add_created_vertex( + *self._create_vertex( + vertex=vertex, + position=position, + label=label, + label_fill_color=label_fill_color, + vertex_type=vertex_type, + vertex_config=vertex_config, + vertex_mobject=vertex_mobject, + ) + ) + + def _create_vertices( + self: "Graph", + *vertices: Hashable, + positions: Optional[dict] = None, + labels: bool = False, + label_fill_color: str = BLACK, + vertex_type: Type["Mobject"] = Dot, + vertex_config: Optional[dict] = None, + vertex_mobjects: Optional[dict] = None, + ) -> Iterable[Tuple[Hashable, np.ndarray, dict, "Mobject"]]: if positions is None: positions = {} if vertex_mobjects is None: @@ -646,7 +662,7 @@ def add_vertices( } return [ - self._add_vertex( + self._create_vertex( v, position=positions[v], label=labels[v], @@ -658,6 +674,57 @@ def add_vertices( for v in vertices ] + def add_vertices( + self: Graph, + *vertices: Hashable, + positions: dict | None = None, + labels: bool = False, + label_fill_color: str = BLACK, + vertex_type: type[Mobject] = Dot, + vertex_config: dict | None = None, + vertex_mobjects: dict | None = None, + ): + """Add a list of vertices to the graph. + + Parameters + ---------- + + vertices + Hashable vertex identifiers. + positions + A dictionary specifying the coordinates where the new vertices should be added. + If ``None``, all vertices are created at the center of the graph. + labels + Controls whether or not the vertex is labeled. If ``False`` (the default), + the vertex is not labeled; if ``True`` it is labeled using its + names (as specified in ``vertex``) via :class:`~.MathTex`. Alternatively, + any :class:`~.Mobject` can be passed to be used as the label. + label_fill_color + Sets the fill color of the default labels generated when ``labels`` + is set to ``True``. Has no effect for other values of ``labels``. + vertex_type + The mobject class used for displaying vertices in the scene. + vertex_config + A dictionary containing keyword arguments to be passed to + the class specified via ``vertex_type``. + vertex_mobjects + A dictionary whose keys are the vertex identifiers, and whose + values are mobjects that should be used as vertices. Overrides + all other vertex customization options. + """ + return [ + self._add_created_vertex(*v) + for v in self._create_vertices( + *vertices, + positions=positions, + labels=labels, + label_fill_color=label_fill_color, + vertex_type=vertex_type, + vertex_config=vertex_config, + vertex_mobjects=vertex_mobjects, + ) + ] + @override_animate(add_vertices) def _add_vertices_animation(self, *args, anim_args=None, **kwargs): if anim_args is None: @@ -665,9 +732,17 @@ def _add_vertices_animation(self, *args, anim_args=None, **kwargs): animation = anim_args.pop("animation", Create) - vertex_mobjects = self.add_vertices(*args, **kwargs) + vertex_mobjects = self._create_vertices(*args, **kwargs) + + def on_finish(scene: "Scene"): + for v in vertex_mobjects: + scene.remove(v[-1]) + self._add_created_vertex(*v) + return AnimationGroup( - *(animation(v, **anim_args) for v in vertex_mobjects), group=self + *(animation(v[-1], **anim_args) for v in vertex_mobjects), + group=self, + _on_finish=on_finish, ) def _remove_vertex(self, vertex): diff --git a/manim/scene/scene.py b/manim/scene/scene.py index b8dc700e6c..9568c43681 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -435,7 +435,7 @@ def add(self, *mobjects): mobjects = [*mobjects, *self.foreground_mobjects] self.restructure_mobjects(to_remove=mobjects) self.mobjects += mobjects - if self.moving_mobjects: + if self.moving_mobjects is not None: self.restructure_mobjects( to_remove=mobjects, mobject_list_name="moving_mobjects", From 4edf4e39837ed9106a0be4ca70def6b9349642a7 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 25 Dec 2021 15:06:12 +0100 Subject: [PATCH 5/9] Update tests --- manim/utils/testing/_frames_testers.py | 2 +- .../vector_scene/vector_to_coords.npz | Bin 204609 -> 205216 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/testing/_frames_testers.py b/manim/utils/testing/_frames_testers.py index 498403a816..a277f88097 100644 --- a/manim/utils/testing/_frames_testers.py +++ b/manim/utils/testing/_frames_testers.py @@ -26,7 +26,7 @@ def testing(self): # For backward compatibility, when the control data contains only one frame (<= v0.8.0) if len(self._frames.shape) != 4: self._frames = np.expand_dims(self._frames, axis=0) - print(self._frames.shape) + logger.debug(self._frames.shape) self._number_frames = np.ma.size(self._frames, axis=0) yield assert self._frames_compared == self._number_frames, ( diff --git a/tests/test_graphical_units/control_data/vector_scene/vector_to_coords.npz b/tests/test_graphical_units/control_data/vector_scene/vector_to_coords.npz index d473bdb36db972e6224fa3adba2c22c1943b6b2b..d7d6eb905dc7230c47614a82680201d2f64f84a8 100644 GIT binary patch delta 9131 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 Rhr?UXd?ira3jVF(zX2sOLE-=a From f6ce8b53af29106ccfb283c988ffee4555084781 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Fri, 7 Jan 2022 11:30:47 +0100 Subject: [PATCH 6/9] Update tests to provide mocked scene --- manim/animation/composition.py | 1 - tests/opengl/test_composition_opengl.py | 11 +++++++++-- tests/test_composition.py | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/manim/animation/composition.py b/manim/animation/composition.py index 2084eb88b2..ac50424f48 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -119,7 +119,6 @@ def interpolate(self, alpha: float) -> None: class Succession(AnimationGroup): def __init__(self, *animations: Animation, lag_ratio: float = 1, **kwargs) -> None: super().__init__(*animations, lag_ratio=lag_ratio, **kwargs) - self.scene = None def begin(self) -> None: assert len(self.animations) > 0 diff --git a/tests/opengl/test_composition_opengl.py b/tests/opengl/test_composition_opengl.py index 8f4580580c..ad2e35ff1b 100644 --- a/tests/opengl/test_composition_opengl.py +++ b/tests/opengl/test_composition_opengl.py @@ -1,4 +1,5 @@ from __future__ import annotations +from unittest.mock import Mock from manim.animation.animation import Animation, Wait from manim.animation.composition import AnimationGroup, Succession @@ -14,6 +15,7 @@ def test_succession_timing(using_opengl_renderer): animation_4s = FadeOut(line, shift=DOWN, run_time=4.0) succession = Succession(animation_1s, animation_4s) assert succession.get_run_time() == 5.0 + succession.setup_scene(Mock()) succession.begin() assert succession.active_index == 0 # The first animation takes 20% of the total run time. @@ -45,6 +47,7 @@ def test_succession_in_succession_timing(using_opengl_renderer): ) assert nested_succession.get_run_time() == 5.0 assert succession.get_run_time() == 10.0 + succession.setup_scene(Mock()) succession.begin() succession.interpolate(0.1) assert succession.active_index == 0 @@ -83,8 +86,12 @@ def test_succession_in_succession_timing(using_opengl_renderer): def test_animationbuilder_in_group(using_opengl_renderer): sqr = Square() circ = Circle() - animation_group = AnimationGroup(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) - assert all(isinstance(anim, Animation) for anim in animation_group.animations) + animation_group = AnimationGroup( + sqr.animate.shift(DOWN).scale(2), FadeIn(circ) + ) + assert all( + isinstance(anim, Animation) for anim in animation_group.animations + ) succession = Succession(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) assert all(isinstance(anim, Animation) for anim in succession.animations) diff --git a/tests/test_composition.py b/tests/test_composition.py index 844e683a89..490deefcd4 100644 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -1,4 +1,5 @@ from __future__ import annotations +from unittest.mock import Mock import pytest @@ -18,6 +19,7 @@ def test_succession_timing(): animation_4s = FadeOut(line, shift=DOWN, run_time=4.0) succession = Succession(animation_1s, animation_4s) assert succession.get_run_time() == 5.0 + succession.setup_scene(Mock()) succession.begin() assert succession.active_index == 0 # The first animation takes 20% of the total run time. @@ -49,6 +51,7 @@ def test_succession_in_succession_timing(): ) assert nested_succession.get_run_time() == 5.0 assert succession.get_run_time() == 10.0 + succession.setup_scene(Mock()) succession.begin() succession.interpolate(0.1) assert succession.active_index == 0 @@ -87,8 +90,12 @@ def test_succession_in_succession_timing(): def test_animationbuilder_in_group(): sqr = Square() circ = Circle() - animation_group = AnimationGroup(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) - assert all(isinstance(anim, Animation) for anim in animation_group.animations) + animation_group = AnimationGroup( + sqr.animate.shift(DOWN).scale(2), FadeIn(circ) + ) + assert all( + isinstance(anim, Animation) for anim in animation_group.animations + ) succession = Succession(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) assert all(isinstance(anim, Animation) for anim in succession.animations) @@ -106,7 +113,8 @@ def test_animationgroup_with_wait(): @pytest.mark.parametrize( - "animation_remover, animation_group_remover", [(False, True), (True, False)] + "animation_remover, animation_group_remover", + [(False, True), (True, False)], ) def test_animationgroup_is_passing_remover_to_animations( animation_remover, animation_group_remover @@ -131,7 +139,9 @@ def test_animationgroup_is_passing_remover_to_nested_animationgroups(): circ_animation = Write(Circle(), remover=True) polygon_animation = Create(RegularPolygon(5)) animation_group = AnimationGroup( - AnimationGroup(sqr_animation, polygon_animation), circ_animation, remover=True + AnimationGroup(sqr_animation, polygon_animation), + circ_animation, + remover=True, ) scene.play(animation_group) From 4ed29a808f412249bb8cb745d4aae8d59d43820e Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Thu, 20 Jan 2022 17:16:12 +0100 Subject: [PATCH 7/9] Make setup_scene internal --- manim/animation/animation.py | 2 +- manim/animation/composition.py | 8 ++++---- manim/scene/scene.py | 2 +- tests/opengl/test_composition_opengl.py | 4 ++-- tests/test_composition.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/manim/animation/animation.py b/manim/animation/animation.py index 2092add785..ece0ae70f5 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -228,7 +228,7 @@ def clean_up_from_scene(self, scene: Scene) -> None: if self.is_remover(): scene.remove(self.mobject) - def setup_scene(self, scene: "Scene") -> None: + def _setup_scene(self, scene: "Scene") -> None: """Setup up the :class:`~.Scene` before starting the animation. This includes to :meth:`~.Scene.add` the Animation's diff --git a/manim/animation/composition.py b/manim/animation/composition.py index ac50424f48..4a1944404b 100644 --- a/manim/animation/composition.py +++ b/manim/animation/composition.py @@ -57,9 +57,9 @@ def begin(self) -> None: for anim in self.animations: anim.begin() - def setup_scene(self, scene) -> None: + def _setup_scene(self, scene) -> None: for anim in self.animations: - anim.setup_scene(scene) + anim._setup_scene(scene) def finish(self) -> None: for anim in self.animations: @@ -132,7 +132,7 @@ def update_mobjects(self, dt: float) -> None: if self.active_animation: self.active_animation.update_mobjects(dt) - def setup_scene(self, scene) -> None: + def _setup_scene(self, scene) -> None: if scene is None: return if self.is_introducer(): @@ -150,7 +150,7 @@ def update_active_animation(self, index: int) -> None: self.active_end_time: float | None = None else: self.active_animation = self.animations[index] - self.active_animation.setup_scene(self.scene) + self.active_animation._setup_scene(self.scene) self.active_animation.begin() self.active_start_time = self.anims_with_timings[index][1] self.active_end_time = self.anims_with_timings[index][2] diff --git a/manim/scene/scene.py b/manim/scene/scene.py index 9568c43681..6273204659 100644 --- a/manim/scene/scene.py +++ b/manim/scene/scene.py @@ -1024,7 +1024,7 @@ def compile_animation_data(self, *animations: Animation, **play_kwargs): def begin_animations(self) -> None: """Start the animations of the scene.""" for animation in self.animations: - animation.setup_scene(self) + animation._setup_scene(self) animation.begin() def is_current_animation_frozen_frame(self) -> bool: diff --git a/tests/opengl/test_composition_opengl.py b/tests/opengl/test_composition_opengl.py index ad2e35ff1b..169a3d4161 100644 --- a/tests/opengl/test_composition_opengl.py +++ b/tests/opengl/test_composition_opengl.py @@ -15,7 +15,7 @@ def test_succession_timing(using_opengl_renderer): animation_4s = FadeOut(line, shift=DOWN, run_time=4.0) succession = Succession(animation_1s, animation_4s) assert succession.get_run_time() == 5.0 - succession.setup_scene(Mock()) + succession._setup_scene(Mock()) succession.begin() assert succession.active_index == 0 # The first animation takes 20% of the total run time. @@ -47,7 +47,7 @@ def test_succession_in_succession_timing(using_opengl_renderer): ) assert nested_succession.get_run_time() == 5.0 assert succession.get_run_time() == 10.0 - succession.setup_scene(Mock()) + succession._setup_scene(Mock()) succession.begin() succession.interpolate(0.1) assert succession.active_index == 0 diff --git a/tests/test_composition.py b/tests/test_composition.py index 490deefcd4..896ba5645e 100644 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -19,7 +19,7 @@ def test_succession_timing(): animation_4s = FadeOut(line, shift=DOWN, run_time=4.0) succession = Succession(animation_1s, animation_4s) assert succession.get_run_time() == 5.0 - succession.setup_scene(Mock()) + succession._setup_scene(Mock()) succession.begin() assert succession.active_index == 0 # The first animation takes 20% of the total run time. @@ -51,7 +51,7 @@ def test_succession_in_succession_timing(): ) assert nested_succession.get_run_time() == 5.0 assert succession.get_run_time() == 10.0 - succession.setup_scene(Mock()) + succession._setup_scene(Mock()) succession.begin() succession.interpolate(0.1) assert succession.active_index == 0 From 7a77ee68cf2829d4abcf728362541729eaa69d30 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Thu, 20 Jan 2022 17:22:59 +0100 Subject: [PATCH 8/9] Replace string annotation with new typehints --- manim/animation/animation.py | 4 ++-- manim/mobject/graph.py | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/manim/animation/animation.py b/manim/animation/animation.py index ece0ae70f5..c597a0bdc0 100644 --- a/manim/animation/animation.py +++ b/manim/animation/animation.py @@ -147,7 +147,7 @@ def __init__( self.introducer: bool = introducer self.suspend_mobject_updating: bool = suspend_mobject_updating self.lag_ratio: float = lag_ratio - self._on_finish: Callable[["Scene"], None] = _on_finish + self._on_finish: Callable[[Scene], None] = _on_finish if config["renderer"] == "opengl": self.starting_mobject: OpenGLMobject = OpenGLMobject() self.mobject: OpenGLMobject = ( @@ -228,7 +228,7 @@ def clean_up_from_scene(self, scene: Scene) -> None: if self.is_remover(): scene.remove(self.mobject) - def _setup_scene(self, scene: "Scene") -> None: + def _setup_scene(self, scene: Scene) -> None: """Setup up the :class:`~.Scene` before starting the animation. This includes to :meth:`~.Scene.add` the Animation's diff --git a/manim/mobject/graph.py b/manim/mobject/graph.py index e0079b62b2..379a4db1de 100644 --- a/manim/mobject/graph.py +++ b/manim/mobject/graph.py @@ -502,13 +502,13 @@ def __repr__(self: Graph) -> str: def _create_vertex( self, vertex: Hashable, - position: Optional[np.ndarray] = None, + position: np.ndarray | None = None, label: bool = False, label_fill_color: str = BLACK, - vertex_type: Type["Mobject"] = Dot, - vertex_config: Optional[dict] = None, - vertex_mobject: Optional[dict] = None, - ) -> Tuple[Hashable, np.ndarray, dict, "Mobject"]: + vertex_type: type[Mobject] = Dot, + vertex_config: dict | None = None, + vertex_mobject: dict | None = None, + ) -> tuple[Hashable, np.ndarray, dict, Mobject]: if position is None: position = self.get_center() @@ -550,8 +550,8 @@ def _add_created_vertex( vertex: Hashable, position: np.ndarray, vertex_config: dict, - vertex_mobject: "Mobject", - ) -> "Mobject": + vertex_mobject: Mobject, + ) -> Mobject: if vertex in self.vertices: raise ValueError( f"Vertex identifier '{vertex}' is already used for a vertex in this graph.", @@ -621,15 +621,15 @@ def _add_vertex( ) def _create_vertices( - self: "Graph", + self: Graph, *vertices: Hashable, - positions: Optional[dict] = None, + positions: dict | None = None, labels: bool = False, label_fill_color: str = BLACK, - vertex_type: Type["Mobject"] = Dot, - vertex_config: Optional[dict] = None, - vertex_mobjects: Optional[dict] = None, - ) -> Iterable[Tuple[Hashable, np.ndarray, dict, "Mobject"]]: + vertex_type: type[Mobject] = Dot, + vertex_config: dict | None = None, + vertex_mobjects: dict | None = None, + ) -> Iterable[tuple[Hashable, np.ndarray, dict, Mobject]]: if positions is None: positions = {} if vertex_mobjects is None: @@ -734,7 +734,7 @@ def _add_vertices_animation(self, *args, anim_args=None, **kwargs): vertex_mobjects = self._create_vertices(*args, **kwargs) - def on_finish(scene: "Scene"): + def on_finish(scene: Scene): for v in vertex_mobjects: scene.remove(v[-1]) self._add_created_vertex(*v) From 26ac33b2989dfbc7d8b3fea01af480daaeea5a7d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Jan 2022 16:24:20 +0000 Subject: [PATCH 9/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/animation/creation.py | 16 ++++------------ tests/opengl/test_composition_opengl.py | 9 +++------ tests/test_composition.py | 9 +++------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/manim/animation/creation.py b/manim/animation/creation.py index ee701ba678..6c592fbd47 100644 --- a/manim/animation/creation.py +++ b/manim/animation/creation.py @@ -121,9 +121,7 @@ def __init__( ): pointwise = getattr(mobject, "pointwise_become_partial", None) if not callable(pointwise): - raise NotImplementedError( - "This animation is not defined for this Mobject." - ) + raise NotImplementedError("This animation is not defined for this Mobject.") super().__init__(mobject, **kwargs) def interpolate_submobject( @@ -174,9 +172,7 @@ def __init__( introducer: bool = True, **kwargs, ) -> None: - super().__init__( - mobject, lag_ratio=lag_ratio, introducer=introducer, **kwargs - ) + super().__init__(mobject, lag_ratio=lag_ratio, introducer=introducer, **kwargs) def _get_bounds(self, alpha: float) -> tuple[int, float]: return (0, alpha) @@ -255,9 +251,7 @@ def __init__( def _typecheck_input(self, vmobject: VMobject | OpenGLVMobject) -> None: if not isinstance(vmobject, (VMobject, OpenGLVMobject)): - raise TypeError( - "DrawBorderThenFill only works for vectorized Mobjects" - ) + raise TypeError("DrawBorderThenFill only works for vectorized Mobjects") def begin(self) -> None: self.outline = self.get_outline() @@ -267,9 +261,7 @@ def get_outline(self) -> Mobject: outline = self.mobject.copy() outline.set_fill(opacity=0) for sm in outline.family_members_with_points(): - sm.set_stroke( - color=self.get_stroke_color(sm), width=self.stroke_width - ) + sm.set_stroke(color=self.get_stroke_color(sm), width=self.stroke_width) return outline def get_stroke_color(self, vmobject: VMobject | OpenGLVMobject) -> Color: diff --git a/tests/opengl/test_composition_opengl.py b/tests/opengl/test_composition_opengl.py index 169a3d4161..9cb293ed41 100644 --- a/tests/opengl/test_composition_opengl.py +++ b/tests/opengl/test_composition_opengl.py @@ -1,4 +1,5 @@ from __future__ import annotations + from unittest.mock import Mock from manim.animation.animation import Animation, Wait @@ -86,12 +87,8 @@ def test_succession_in_succession_timing(using_opengl_renderer): def test_animationbuilder_in_group(using_opengl_renderer): sqr = Square() circ = Circle() - animation_group = AnimationGroup( - sqr.animate.shift(DOWN).scale(2), FadeIn(circ) - ) - assert all( - isinstance(anim, Animation) for anim in animation_group.animations - ) + animation_group = AnimationGroup(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) + assert all(isinstance(anim, Animation) for anim in animation_group.animations) succession = Succession(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) assert all(isinstance(anim, Animation) for anim in succession.animations) diff --git a/tests/test_composition.py b/tests/test_composition.py index 896ba5645e..a1a5f22f17 100644 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -1,4 +1,5 @@ from __future__ import annotations + from unittest.mock import Mock import pytest @@ -90,12 +91,8 @@ def test_succession_in_succession_timing(): def test_animationbuilder_in_group(): sqr = Square() circ = Circle() - animation_group = AnimationGroup( - sqr.animate.shift(DOWN).scale(2), FadeIn(circ) - ) - assert all( - isinstance(anim, Animation) for anim in animation_group.animations - ) + animation_group = AnimationGroup(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) + assert all(isinstance(anim, Animation) for anim in animation_group.animations) succession = Succession(sqr.animate.shift(DOWN).scale(2), FadeIn(circ)) assert all(isinstance(anim, Animation) for anim in succession.animations)