From 326728d3b417ea84850bae3d7c3417ecff53e13d Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Mon, 30 Oct 2023 21:22:51 -0400 Subject: [PATCH 1/8] Fix CSV reader adding empty files Fixes issue #3311 --- manim/utils/docbuild/manim_directive.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/manim/utils/docbuild/manim_directive.py b/manim/utils/docbuild/manim_directive.py index 5b744985ca..37516110e0 100644 --- a/manim/utils/docbuild/manim_directive.py +++ b/manim/utils/docbuild/manim_directive.py @@ -342,6 +342,9 @@ def _log_rendering_times(*args): sys.exit() print("\nRendering Summary\n-----------------\n") + + # filter out empty lists caused by csv reader + data = [row for row in data if row] max_file_length = max(len(row[0]) for row in data) for key, group in it.groupby(data, key=lambda row: row[0]): From 5e8a7636b713093f650bdc8909c32bb3aeedc539 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 31 Oct 2023 01:27:14 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- manim/utils/docbuild/manim_directive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/utils/docbuild/manim_directive.py b/manim/utils/docbuild/manim_directive.py index 37516110e0..c369464679 100644 --- a/manim/utils/docbuild/manim_directive.py +++ b/manim/utils/docbuild/manim_directive.py @@ -342,7 +342,7 @@ def _log_rendering_times(*args): sys.exit() print("\nRendering Summary\n-----------------\n") - + # filter out empty lists caused by csv reader data = [row for row in data if row] From d88e307ccd88a3bf3e4b137fc59af99fb4925bf7 Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Fri, 3 Nov 2023 16:14:59 -0400 Subject: [PATCH 3/8] Added LinearTransformationScene.ghost_vectors --- manim/scene/vector_space_scene.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/manim/scene/vector_space_scene.py b/manim/scene/vector_space_scene.py index f651c2c86a..b1c218a9af 100644 --- a/manim/scene/vector_space_scene.py +++ b/manim/scene/vector_space_scene.py @@ -616,6 +616,8 @@ def __init__( }, } + self.ghost_vectors = VGroup() + self.foreground_plane_kwargs = { "x_range": np.array([-config["frame_width"], config["frame_width"], 1.0]), "y_range": np.array([-config["frame_width"], config["frame_width"], 1.0]), @@ -997,7 +999,9 @@ def get_piece_movement(self, pieces: list | tuple | np.ndarray): start = VGroup(*pieces) target = VGroup(*(mob.target for mob in pieces)) if self.leave_ghost_vectors: - self.add(start.copy().fade(0.7)) + # start.copy() gives a VGroup of Vectors + self.ghost_vectors.add(start.copy().fade(0.7)) + self.add(self.ghost_vectors[-1]) return Transform(start, target, lag_ratio=0) def get_moving_mobject_movement(self, func: Callable[[np.ndarray], np.ndarray]): From efecbcbb36da80cc54a3a945f8bf58ef91a38df0 Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Sat, 4 Nov 2023 14:38:05 -0400 Subject: [PATCH 4/8] Added test and prevented empty VGroups as ghost vectors --- manim/scene/vector_space_scene.py | 10 +++++++- tests/test_linear_transformation_scene.py | 28 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 tests/test_linear_transformation_scene.py diff --git a/manim/scene/vector_space_scene.py b/manim/scene/vector_space_scene.py index b1c218a9af..e6c0f97896 100644 --- a/manim/scene/vector_space_scene.py +++ b/manim/scene/vector_space_scene.py @@ -743,6 +743,13 @@ def add_moving_mobject( mobject.target = target_mobject self.add_special_mobjects(self.moving_mobjects, mobject) + def get_ghost_vectors(self) -> VGroup: + """ + Returns all ghost vectors ever added to ``self``. Each element is a ``VGroup`` of + two ghost vectors. + """ + return self.ghost_vectors + def get_unit_square( self, color: str = YELLOW, opacity: float = 0.3, stroke_width: float = 3 ): @@ -998,7 +1005,8 @@ def get_piece_movement(self, pieces: list | tuple | np.ndarray): """ start = VGroup(*pieces) target = VGroup(*(mob.target for mob in pieces)) - if self.leave_ghost_vectors: + # don't add empty VGroups + if self.leave_ghost_vectors and start.submobjects: # start.copy() gives a VGroup of Vectors self.ghost_vectors.add(start.copy().fade(0.7)) self.add(self.ghost_vectors[-1]) diff --git a/tests/test_linear_transformation_scene.py b/tests/test_linear_transformation_scene.py new file mode 100644 index 0000000000..35348fd4c1 --- /dev/null +++ b/tests/test_linear_transformation_scene.py @@ -0,0 +1,28 @@ +from manim import LinearTransformationScene, Vector, VGroup, UP, RIGHT + + +__module_test__ = "vector_space_scene" + + + +def test_ghost_vectors_len_and_types(): + scene = LinearTransformationScene() + scene.leave_ghost_vectors = True + + # prepare vectors (they require a vmobject as their target) + v1, v2 = Vector(RIGHT), Vector(RIGHT) + v1.target, v2.target = Vector(UP), Vector(UP) + + # ghost_vector addition is in this method + scene.get_piece_movement((v1, v2)) + + ghosts = scene.get_ghost_vectors() + assert len(ghosts) == 1 + # check if there are two vectors in the ghost vector VGroup + assert len(ghosts[0]) == 2 + + # check types of ghost vectors + assert isinstance(ghosts, VGroup) and isinstance(ghosts[0], VGroup) + assert all(isinstance(x, Vector) for x in ghosts[0]) + + From d0ac9ae4a70a4e159d46f8d88e133b233a41ef1b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Nov 2023 18:39:13 +0000 Subject: [PATCH 5/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_linear_transformation_scene.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/test_linear_transformation_scene.py b/tests/test_linear_transformation_scene.py index 35348fd4c1..c45e3f6997 100644 --- a/tests/test_linear_transformation_scene.py +++ b/tests/test_linear_transformation_scene.py @@ -1,10 +1,8 @@ -from manim import LinearTransformationScene, Vector, VGroup, UP, RIGHT - +from manim import RIGHT, UP, LinearTransformationScene, Vector, VGroup __module_test__ = "vector_space_scene" - def test_ghost_vectors_len_and_types(): scene = LinearTransformationScene() scene.leave_ghost_vectors = True @@ -24,5 +22,3 @@ def test_ghost_vectors_len_and_types(): # check types of ghost vectors assert isinstance(ghosts, VGroup) and isinstance(ghosts[0], VGroup) assert all(isinstance(x, Vector) for x in ghosts[0]) - - From 70c510b154a9dd47e7eb636613f0212d4d86e7b0 Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Sat, 4 Nov 2023 15:17:16 -0400 Subject: [PATCH 6/8] Fixed typo in example --- manim/scene/vector_space_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manim/scene/vector_space_scene.py b/manim/scene/vector_space_scene.py index e6c0f97896..d39ce32895 100644 --- a/manim/scene/vector_space_scene.py +++ b/manim/scene/vector_space_scene.py @@ -572,7 +572,7 @@ def __init__(self, **kwargs): self, show_coordinates=True, leave_ghost_vectors=True, - *kwargs + **kwargs ) def construct(self): From dee29c390f433bb3964c13b2f6ccc991c18db925 Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Mon, 6 Nov 2023 11:38:45 -0500 Subject: [PATCH 7/8] Added ability to join together multiple renders --- manim/__init__.py | 1 + manim/utils/join_scenes.py | 121 +++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 manim/utils/join_scenes.py diff --git a/manim/__init__.py b/manim/__init__.py index cae45fcc42..dc33514184 100644 --- a/manim/__init__.py +++ b/manim/__init__.py @@ -100,6 +100,7 @@ from .utils.space_ops import * from .utils.tex import * from .utils.tex_templates import * +from .utils.join_scenes import * try: from IPython import get_ipython diff --git a/manim/utils/join_scenes.py b/manim/utils/join_scenes.py new file mode 100644 index 0000000000..683a228246 --- /dev/null +++ b/manim/utils/join_scenes.py @@ -0,0 +1,121 @@ +from pathlib import Path +import subprocess + +from manim._config import config, tempconfig, logger +from manim.constants import QUALITIES +from manim.scene.scene import Scene +from manim.__main__ import __version__ + +__all__ = ["render_multiple_scenes"] + +def combine_renders( + dir: Path, + input_files: list[str], + output_file: Path, + ) -> None: + """Make ffmpeg do all the hard work of combining video files""" + + file_list = dir / "partial_scenes_file_list.txt" + logger.debug( + f"Renders to combine ({len(input_files)} files): %(p)s", + {"p": input_files[:5]}, + ) + with file_list.open("w", encoding="utf-8") as fp: + fp.write("# This file is used internally by FFMPEG.\n") + for pf_path in input_files: + pf_path = Path(pf_path).as_posix() + fp.write(f"file 'file:{pf_path}'\n") + commands = [ + config.ffmpeg_executable, + "-y", # overwrite output file if it exists + "-f", + "concat", + "-safe", + "0", + "-i", + str(file_list), + "-loglevel", + config.ffmpeg_loglevel.lower(), + "-metadata", + f"comment=Rendered with Manim Community v{__version__}", + "-nostdin", + "-c", + "copy", + str(output_file) + ] + + combine_process = subprocess.Popen(commands) + combine_process.wait() + +def combine_scenes( + *classes: type[Scene], + output: Path | None = None + ) -> Path: + """ + Combine scenes produced by different renders. This function is mostly Path stuff. + + Returns: + -------- + The location of the final file + """ + quality_dict = QUALITIES[config.quality] # type: ignore + quality_folder = f"{quality_dict['pixel_height']}p{quality_dict['frame_rate']}" + + dir = Path(config.media_dir) / f"videos/{quality_folder}" + + files = [ + str(dir / f"{cls.__name__}.mp4") + for cls in classes + ] + + output = dir/"final.mp4" if output is None else output + + combine_renders( + dir, + files, + output + ) + return output + +def render_multiple_scenes( + *scenes: type[Scene], + dir: Path | str = config.media_dir, + user_config: dict = {}, + output_file: Path | str | None = None + ) -> None: + """ + Render and concatenate multiple `Scene`s together. + + Parameters: + ----------- + *scenes + The classes to combine + dir + The location of the media files + user_config + Other configuration options, like transparency and stuff. Some features (like turning into a GIF) may not work + output_file + Where to keep the final file + """ + if not scenes: + raise ValueError("Must be at least one scene to render!") + + if not Path(dir).exists(): + Path(dir).mkdir() + config.media_dir = str(dir) + + config.update(user_config) + + for scene in scenes: + # use tempconfig to prevent bugs + # with multiple renders in one file + with tempconfig({}): + scene().render() + + if output_file is not None: + output_file = Path(output_file) + final = combine_scenes(*scenes, output=output_file) + + logger.info(f"Concatenated Scene can be found at \'{final.absolute()}\'") + + From 8ce4460853e1fb0b4013b4f51caf12bca8561ddb Mon Sep 17 00:00:00 2001 From: JasonGrace2282 Date: Mon, 6 Nov 2023 11:40:31 -0500 Subject: [PATCH 8/8] Revert "Added ability to join together multiple renders" (wrong branch) This reverts commit dee29c390f433bb3964c13b2f6ccc991c18db925. --- manim/__init__.py | 1 - manim/utils/join_scenes.py | 121 ------------------------------------- 2 files changed, 122 deletions(-) delete mode 100644 manim/utils/join_scenes.py diff --git a/manim/__init__.py b/manim/__init__.py index dc33514184..cae45fcc42 100644 --- a/manim/__init__.py +++ b/manim/__init__.py @@ -100,7 +100,6 @@ from .utils.space_ops import * from .utils.tex import * from .utils.tex_templates import * -from .utils.join_scenes import * try: from IPython import get_ipython diff --git a/manim/utils/join_scenes.py b/manim/utils/join_scenes.py deleted file mode 100644 index 683a228246..0000000000 --- a/manim/utils/join_scenes.py +++ /dev/null @@ -1,121 +0,0 @@ -from pathlib import Path -import subprocess - -from manim._config import config, tempconfig, logger -from manim.constants import QUALITIES -from manim.scene.scene import Scene -from manim.__main__ import __version__ - -__all__ = ["render_multiple_scenes"] - -def combine_renders( - dir: Path, - input_files: list[str], - output_file: Path, - ) -> None: - """Make ffmpeg do all the hard work of combining video files""" - - file_list = dir / "partial_scenes_file_list.txt" - logger.debug( - f"Renders to combine ({len(input_files)} files): %(p)s", - {"p": input_files[:5]}, - ) - with file_list.open("w", encoding="utf-8") as fp: - fp.write("# This file is used internally by FFMPEG.\n") - for pf_path in input_files: - pf_path = Path(pf_path).as_posix() - fp.write(f"file 'file:{pf_path}'\n") - commands = [ - config.ffmpeg_executable, - "-y", # overwrite output file if it exists - "-f", - "concat", - "-safe", - "0", - "-i", - str(file_list), - "-loglevel", - config.ffmpeg_loglevel.lower(), - "-metadata", - f"comment=Rendered with Manim Community v{__version__}", - "-nostdin", - "-c", - "copy", - str(output_file) - ] - - combine_process = subprocess.Popen(commands) - combine_process.wait() - -def combine_scenes( - *classes: type[Scene], - output: Path | None = None - ) -> Path: - """ - Combine scenes produced by different renders. This function is mostly Path stuff. - - Returns: - -------- - The location of the final file - """ - quality_dict = QUALITIES[config.quality] # type: ignore - quality_folder = f"{quality_dict['pixel_height']}p{quality_dict['frame_rate']}" - - dir = Path(config.media_dir) / f"videos/{quality_folder}" - - files = [ - str(dir / f"{cls.__name__}.mp4") - for cls in classes - ] - - output = dir/"final.mp4" if output is None else output - - combine_renders( - dir, - files, - output - ) - return output - -def render_multiple_scenes( - *scenes: type[Scene], - dir: Path | str = config.media_dir, - user_config: dict = {}, - output_file: Path | str | None = None - ) -> None: - """ - Render and concatenate multiple `Scene`s together. - - Parameters: - ----------- - *scenes - The classes to combine - dir - The location of the media files - user_config - Other configuration options, like transparency and stuff. Some features (like turning into a GIF) may not work - output_file - Where to keep the final file - """ - if not scenes: - raise ValueError("Must be at least one scene to render!") - - if not Path(dir).exists(): - Path(dir).mkdir() - config.media_dir = str(dir) - - config.update(user_config) - - for scene in scenes: - # use tempconfig to prevent bugs - # with multiple renders in one file - with tempconfig({}): - scene().render() - - if output_file is not None: - output_file = Path(output_file) - final = combine_scenes(*scenes, output=output_file) - - logger.info(f"Concatenated Scene can be found at \'{final.absolute()}\'") - -