Exemplo n.º 1
0
def _determine_quality(args):
    old_qualities = {
        "k": "fourk_quality",
        "e": "high_quality",
        "m": "medium_quality",
        "l": "low_quality",
    }

    for quality in constants.QUALITIES:
        if quality == constants.DEFAULT_QUALITY:
            # Skip so we prioritize anything that overwrites the default quality.
            pass
        elif getattr(args, quality, None) or (
            hasattr(args, "quality") and args.quality == constants.QUALITIES[quality]
        ):
            return quality

    for quality in old_qualities:
        if getattr(args, quality, None):
            logger.warning(
                f"Option -{quality} is deprecated please use the --quality/-q flag."
            )
            return old_qualities[quality]

    return constants.DEFAULT_QUALITY
Exemplo n.º 2
0
 def should_create_window(self):
     if config["force_window"]:
         logger.warning(
             "'--force_window' is enabled, this is intended for debugging purposes "
             "and may impact performance if used when outputting files", )
         return True
     return (config["preview"] and not config["save_last_frame"]
             and not config["format"] and not config["write_to_movie"]
             and not config["dry_run"])
Exemplo n.º 3
0
    def setup(self):
        """
        This method is used internally by Manim
        to set up the scene for proper use.
        """
        logger.warning(
            "GraphScene is in the process of being deprecated (outdated) "
            "in favour of TwoDScene. Please use TwoDScene in the future"
        )

        self.default_graph_colors_cycle = it.cycle(self.default_graph_colors)

        self.left_T_label = VGroup()
        self.left_v_line = VGroup()
        self.right_T_label = VGroup()
        self.right_v_line = VGroup()
    def handle_transforms(self, element, mobject):
        """Applies the SVG transform to the specified mobject. Transforms include:
        ``matrix``, ``translate``, and ``scale``.

        Parameters
        ----------
        element : :class:`minidom.Element`
            The transform command to perform

        mobject : :class:`Mobject`
            The Mobject to transform.
        """

        if element.hasAttribute("x") and element.hasAttribute("y"):
            x = self.attribute_to_float(element.getAttribute("x"))
            # Flip y
            y = -self.attribute_to_float(element.getAttribute("y"))
            mobject.shift(x * RIGHT + y * UP)

        transform_attr_value = element.getAttribute("transform")

        # parse the various transforms in the attribute value
        transform_names = [
            "matrix", "translate", "scale", "rotate", "skewX", "skewY"
        ]

        # Borrowed/Inspired from:
        # https://github.com/cjlano/svg/blob/3ea3384457c9780fa7d67837c9c5fd4ebc42cb3b/svg/svg.py#L75

        # match any SVG transformation with its parameter (until final parenthese)
        # [^)]*    == anything but a closing parenthese
        # '|'.join == OR-list of SVG transformations
        transform_regex = "|".join([x + r"[^)]*\)" for x in transform_names])
        transforms = re.findall(transform_regex, transform_attr_value)

        number_regex = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"

        for t in transforms:
            op_name, op_args = t.split("(")
            op_name = op_name.strip()
            op_args = [float(x) for x in re.findall(number_regex, op_args)]

            if op_name == "matrix":
                transform_args = np.array(op_args).reshape([3, 2])
                x = transform_args[2][0]
                y = -transform_args[2][1]
                matrix = np.identity(self.dim)
                matrix[:2, :2] = transform_args[:2, :]
                matrix[1] *= -1
                matrix[:, 1] *= -1

                for mob in mobject.family_members_with_points():
                    mob.points = np.dot(mob.points, matrix)
                mobject.shift(x * RIGHT + y * UP)

            elif op_name == "scale":
                scale_values = op_args
                if len(scale_values) == 2:
                    scale_x, scale_y = scale_values
                    mobject.scale(np.array([scale_x, scale_y, 1]),
                                  about_point=ORIGIN)
                elif len(scale_values) == 1:
                    scale = scale_values[0]
                    mobject.scale(np.array([scale, scale, 1]),
                                  about_point=ORIGIN)

            elif op_name == "translate":
                if len(op_args) == 2:
                    x, y = op_args
                else:
                    x = op_args
                    y = 0
                mobject.shift(x * RIGHT + y * DOWN)

            else:
                # TODO: handle rotate, skewX and skewY
                # for now adding a warning message
                logger.warning(
                    "Handling of %s transform is not supported yet!", op_name)
Exemplo n.º 5
0
        def manim(
            self,
            line: str,
            cell: str = None,
            local_ns: dict[str, Any] = None,
        ) -> None:
            r"""Render Manim scenes contained in IPython cells.
            Works as a line or cell magic.

            .. hint::

                This line and cell magic works best when used in a JupyterLab
                environment: while all of the functionality is available for
                classic Jupyter notebooks as well, it is possible that videos
                sometimes don't update on repeated execution of the same cell
                if the scene name stays the same.

                This problem does not occur when using JupyterLab.

            Please refer to `<https://jupyter.org/>`_ for more information about JupyterLab
            and Jupyter notebooks.

            Usage in line mode::

                %manim [CLI options] MyAwesomeScene

            Usage in cell mode::

                %%manim [CLI options] MyAwesomeScene

                class MyAweseomeScene(Scene):
                    def construct(self):
                        ...

            Run ``%manim --help`` and ``%manim render --help`` for possible command line interface options.

            .. note::

                The maximal width of the rendered videos that are displayed in the notebook can be
                configured via the ``media_width`` configuration option. The default is set to ``25vw``,
                which is 25% of your current viewport width. To allow the output to become as large
                as possible, set ``config.media_width = "100%"``.

            Examples
            --------

            First make sure to put ``import manim``, or even ``from manim import *``
            in a cell and evaluate it. Then, a typical Jupyter notebook cell for Manim
            could look as follows::

                %%manim -v WARNING --disable_caching -qm BannerExample

                config.media_width = "75%"

                class BannerExample(Scene):
                    def construct(self):
                        self.camera.background_color = "#ece6e2"
                        banner_large = ManimBanner(dark_theme=False).scale(0.7)
                        self.play(banner_large.create())
                        self.play(banner_large.expand())

            Evaluating this cell will render and display the ``BannerExample`` scene defined in the body of the cell.

            .. note::

                In case you want to hide the red box containing the output progress bar, the ``progress_bar`` config
                option should be set to ``None``. This can also be done by passing ``--progress_bar None`` as a
                CLI flag.

            """
            if cell:
                exec(cell, local_ns)

            args = line.split()
            if not len(args) or "-h" in args or "--help" in args or "--version" in args:
                main(args, standalone_mode=False, prog_name="manim")
                return
            modified_args = self.add_additional_args(args)
            args = main(modified_args, standalone_mode=False, prog_name="manim")
            with tempconfig(local_ns.get("config", {})):
                config.digest_args(args)

                renderer = None
                if config.renderer == "opengl":
                    # Check if the imported mobjects extend the OpenGLMobject class
                    # meaning ConvertToOpenGL did its job
                    if "OpenGLMobject" in map(lambda cls: cls.__name__, Group.mro()):
                        from manim.renderer.opengl_renderer import OpenGLRenderer

                        renderer = OpenGLRenderer()
                    else:
                        logger.warning(
                            "Renderer must be set to OpenGL in the configuration file "
                            "before importing Manim! Using cairo renderer instead.",
                        )
                        config.renderer = "cairo"

                try:
                    SceneClass = local_ns[config["scene_names"][0]]
                    scene = SceneClass(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

                local_path = Path(config["output_file"]).relative_to(Path.cwd())
                tmpfile = (
                    Path(config["media_dir"])
                    / "jupyter"
                    / f"{_generate_file_name()}{local_path.suffix}"
                )

                if local_path in self.rendered_files:
                    self.rendered_files[local_path].unlink()
                self.rendered_files[local_path] = tmpfile
                os.makedirs(tmpfile.parent, exist_ok=True)
                shutil.copy(local_path, tmpfile)

                file_type = mimetypes.guess_type(config["output_file"])[0]
                if file_type.startswith("image"):
                    display(Image(filename=config["output_file"]))
                    return

                # videos need to be embedded when running in google colab
                video_embed = "google.colab" in str(get_ipython())

                display(
                    Video(
                        tmpfile,
                        html_attributes=f'controls autoplay loop style="max-width: {config["media_width"]};"',
                        embed=video_embed,
                    ),
                )
Exemplo n.º 6
0
 def __init__(self, *args, **kwargs):
     logger.warning(
         "VMobjectFromSVGPathstring has been deprecated in favour "
         "of SVGPathMobject. Please use SVGPathMobject instead.")
     SVGPathMobject.__init__(self, *args, **kwargs)
Exemplo n.º 7
0
def update_config_with_cli(args):
    """Update the config dictionaries after parsing CLI flags."""
    parser = make_config_parser()
    default = parser["CLI"]

    ## Update config
    global config

    # Handle the *_quality flags.  These determine the section to read
    # and are stored in 'camera_config'.  Note the highest resolution
    # passed as argument will be used.
    quality = _determine_quality(args)
    section = parser[quality if quality != constants.DEFAULT_QUALITY else "CLI"]

    # Loop over low quality for the keys, could be any quality really
    config.update({opt: section.getint(opt) for opt in parser["low_quality"]})

    # The -r, --resolution flag overrides the *_quality flags
    if args.resolution is not None:
        if "," in args.resolution:
            height_str, width_str = args.resolution.split(",")
            height, width = int(height_str), int(width_str)
        else:
            height = int(args.resolution)
            width = int(16 * height / 9)
        config.update({"pixel_height": height, "pixel_width": width})

    # Handle the -c (--background_color) flag
    if args.background_color is not None:
        try:
            background_color = colour.Color(args.background_color)
        except AttributeError as err:
            logger.warning("Please use a valid color.")
            logger.error(err)
            sys.exit(2)
    else:
        background_color = colour.Color(default["background_color"])
    config["background_color"] = background_color

    config["use_js_renderer"] = args.use_js_renderer or default.getboolean(
        "use_js_renderer"
    )
    config["js_renderer_path"] = args.js_renderer_path or default.get(
        "js_renderer_path"
    )

    # Set the rest of the frame properties
    config["frame_height"] = 8.0
    config["frame_width"] = (
        config["frame_height"] * config["pixel_width"] / config["pixel_height"]
    )
    config["frame_y_radius"] = config["frame_height"] / 2
    config["frame_x_radius"] = config["frame_width"] / 2
    config["top"] = config["frame_y_radius"] * constants.UP
    config["bottom"] = config["frame_y_radius"] * constants.DOWN
    config["left_side"] = config["frame_x_radius"] * constants.LEFT
    config["right_side"] = config["frame_x_radius"] * constants.RIGHT

    # Handle the --tex_template flag, if the flag is absent read it from the config.
    if args.tex_template:
        tex_fn = os.path.expanduser(args.tex_template)
    else:
        tex_fn = default["tex_template"] if default["tex_template"] != "" else None

    if tex_fn is not None and not os.access(tex_fn, os.R_OK):
        # custom template not available, fallback to default
        logger.warning(
            f"Custom TeX template {tex_fn} not found or not readable. "
            "Falling back to the default template."
        )
        tex_fn = None
    config["tex_template_file"] = tex_fn
    config["tex_template"] = (
        TexTemplateFromFile(filename=tex_fn) if tex_fn is not None else TexTemplate()
    )

    ## Update file_writer_config
    fw_config = {}

    if config["use_js_renderer"]:
        fw_config["disable_caching"] = True

    if not hasattr(args, "subcommands"):
        fw_config["input_file"] = args.file if args.file else ""
        fw_config["scene_names"] = (
            args.scene_names if args.scene_names is not None else []
        )
        fw_config["output_file"] = args.output_file if args.output_file else ""

    # Note ConfigParser options are all strings and each needs to be converted
    # to the appropriate type.
    for boolean_opt in [
        "preview",
        "show_in_file_browser",
        "leave_progress_bars",
        "write_to_movie",
        "save_last_frame",
        "save_pngs",
        "save_as_gif",
        "write_all",
        "disable_caching",
        "flush_cache",
        "log_to_file",
    ]:
        attr = getattr(args, boolean_opt)
        fw_config[boolean_opt] = (
            default.getboolean(boolean_opt) if attr is None else attr
        )
    # for str_opt in ['media_dir', 'video_dir', 'tex_dir', 'text_dir']:
    for str_opt in ["media_dir"]:
        attr = getattr(args, str_opt)
        fw_config[str_opt] = os.path.relpath(default[str_opt]) if attr is None else attr
    attr = getattr(args, "log_dir")
    fw_config["log_dir"] = (
        os.path.join(fw_config["media_dir"], default["log_dir"])
        if attr is None
        else attr
    )
    dir_names = {
        "video_dir": "videos",
        "images_dir": "images",
        "tex_dir": "Tex",
        "text_dir": "texts",
    }
    for name in dir_names:
        fw_config[name] = os.path.join(fw_config["media_dir"], dir_names[name])

    # the --custom_folders flag overrides the default folder structure with the
    # custom folders defined in the [custom_folders] section of the config file
    fw_config["custom_folders"] = args.custom_folders
    if fw_config["custom_folders"]:
        fw_config["media_dir"] = parser["custom_folders"].get("media_dir")
        for opt in ["video_dir", "images_dir", "tex_dir", "text_dir"]:
            fw_config[opt] = parser["custom_folders"].get(opt)

    # Handle the -s (--save_last_frame) flag: invalidate the -w flag
    # At this point the save_last_frame option has already been set by
    # both CLI and the cfg file, so read the config dict directly
    if fw_config["save_last_frame"]:
        fw_config["write_to_movie"] = False

    # Handle the -t (--transparent) flag.  This flag determines which
    # section to use from the .cfg file.
    section = parser["transparent"] if args.transparent else default
    for opt in ["png_mode", "movie_file_extension", "background_opacity"]:
        fw_config[opt] = section[opt]

    # Handle the -n flag.  Read first from the cfg and then override with CLI.
    # These two are integers -- use getint()
    for opt in ["from_animation_number", "upto_animation_number"]:
        fw_config[opt] = default.getint(opt)
    if fw_config["upto_animation_number"] == -1:
        fw_config["upto_animation_number"] = float("inf")
    nflag = args.from_animation_number
    if nflag is not None:
        if "," in nflag:
            start, end = nflag.split(",")
            fw_config["from_animation_number"] = int(start)
            fw_config["upto_animation_number"] = int(end)
        else:
            fw_config["from_animation_number"] = int(nflag)

    # Handle the --dry_run flag.  This flag determines which section
    # to use from the .cfg file.  All options involved are boolean.
    # Note this overrides the flags -w, -s, -a, -g, and -i.
    if args.dry_run:
        for opt in [
            "write_to_movie",
            "save_last_frame",
            "save_pngs",
            "save_as_gif",
            "write_all",
        ]:
            fw_config[opt] = parser["dry_run"].getboolean(opt)
    if not fw_config["write_to_movie"]:
        fw_config["disable_caching"] = True
    # Read in the streaming section -- all values are strings
    fw_config["streaming"] = {
        opt: parser["streaming"][opt]
        for opt in [
            "live_stream_name",
            "twitch_stream_key",
            "streaming_protocol",
            "streaming_ip",
            "streaming_protocol",
            "streaming_client",
            "streaming_port",
            "streaming_port",
            "streaming_console_banner",
        ]
    }

    # For internal use (no CLI flag)
    fw_config["skip_animations"] = fw_config["save_last_frame"]
    fw_config["max_files_cached"] = default.getint("max_files_cached")
    if fw_config["max_files_cached"] == -1:
        fw_config["max_files_cached"] = float("inf")
    # Parse the verbosity flag to read in the log level
    verbosity = getattr(args, "verbosity")
    verbosity = default["verbosity"] if verbosity is None else verbosity
    fw_config["verbosity"] = verbosity
    logger.setLevel(verbosity)

    # Parse the ffmpeg log level in the config
    ffmpeg_loglevel = parser["ffmpeg"].get("loglevel", None)
    fw_config["ffmpeg_loglevel"] = (
        constants.FFMPEG_VERBOSITY_MAP[verbosity]
        if ffmpeg_loglevel is None
        else ffmpeg_loglevel
    )

    # Parse the progress_bar flag
    progress_bar = getattr(args, "progress_bar")
    if progress_bar is None:
        progress_bar = default.getboolean("progress_bar")
    fw_config["progress_bar"] = progress_bar

    global file_writer_config
    file_writer_config.update(fw_config)