Esempio n. 1
0
def main():
    console.print(f"Manim Community [green]v{__version__}[/green]")
    args = parse_args(sys.argv)

    if hasattr(args, "cmd"):
        if args.cmd == "cfg":
            if args.subcmd:
                from manim._config import cfg_subcmds

                if args.subcmd == "write":
                    cfg_subcmds.write(args.level, args.open)
                elif args.subcmd == "show":
                    cfg_subcmds.show()
                elif args.subcmd == "export":
                    cfg_subcmds.export(args.dir)
            else:
                logger.error("No subcommand provided; Exiting...")

        elif args.cmd == "plugins":
            from manim.plugins import plugins_flags

            if args.list:
                plugins_flags.list_plugins()
            elif not args.list:
                logger.error("No flag provided; Exiting...")

        # elif args.cmd == "some_other_cmd":
        #     something_else_here()

    else:
        config.digest_args(args)
        input_file = config.get_dir("input_file")
        if config["use_webgl_renderer"]:
            try:
                from manim.grpc.impl import frame_server_impl

                server = frame_server_impl.get(input_file)
                server.start()
                server.wait_for_termination()
            except ModuleNotFoundError as e:
                print("\n\n")
                print("Dependencies for the WebGL render are missing. Run "
                      "pip install manim[webgl_renderer] to install them.")
                print(e)
                print("\n\n")
        else:
            for SceneClass in scene_classes_from_file(input_file):
                try:
                    scene = SceneClass()
                    scene.render()
                    open_file_if_needed(scene.renderer.file_writer)
                except Exception:
                    print("\n\n")
                    traceback.print_exc()
                    print("\n\n")
Esempio n. 2
0
def main():
    args = parse_args(sys.argv)

    if hasattr(args, "cmd"):
        if args.cmd == "cfg":
            if args.subcmd:
                from manim._config import cfg_subcmds

                if args.subcmd == "write":
                    cfg_subcmds.write(args.level, args.open)
                elif args.subcmd == "show":
                    cfg_subcmds.show()
                elif args.subcmd == "export":
                    cfg_subcmds.export(args.dir)
            else:
                logger.error("No subcommand provided; Exiting...")

        elif args.cmd == "plugins":
            from manim.plugins import plugins_flags

            if args.list:
                plugins_flags.list_plugins()
            elif not args.list:
                logger.error("No flag provided; Exiting...")

        # elif args.cmd == "some_other_cmd":
        #     something_else_here()

    else:
        config.digest_args(args)
        input_file = config.get_dir("input_file")
        if config["use_js_renderer"]:
            try:
                if frame_server_impl is None:
                    raise ImportError(
                        "Dependencies for JS renderer is not installed.")
                server = frame_server_impl.get(input_file)
                server.start()
                server.wait_for_termination()
            except Exception:
                print("\n\n")
                traceback.print_exc()
                print("\n\n")
        else:
            for SceneClass in scene_classes_from_file(input_file):
                try:
                    scene = SceneClass()
                    scene.render()
                    open_file_if_needed(scene.renderer.file_writer)
                except Exception:
                    print("\n\n")
                    traceback.print_exc()
                    print("\n\n")
Esempio n. 3
0
    def __init__(
        self,
        *tex_strings,
        arg_separator: str = " ",
        substrings_to_isolate: Iterable[str] | None = None,
        tex_to_color_map: dict[str, Color] = None,
        tex_environment: str = "align*",
        **kwargs,
    ):
        self.tex_template = kwargs.pop("tex_template", config["tex_template"])
        self.arg_separator = arg_separator
        self.substrings_to_isolate = (
            [] if substrings_to_isolate is None else substrings_to_isolate
        )
        self.tex_to_color_map = tex_to_color_map
        if self.tex_to_color_map is None:
            self.tex_to_color_map = {}
        self.tex_environment = tex_environment
        self.brace_notation_split_occurred = False
        self.tex_strings = self._break_up_tex_strings(tex_strings)
        try:
            super().__init__(
                self.arg_separator.join(self.tex_strings),
                tex_environment=self.tex_environment,
                tex_template=self.tex_template,
                **kwargs,
            )
            self._break_up_by_substrings()
        except ValueError as compilation_error:
            if self.brace_notation_split_occurred:
                logger.error(
                    dedent(
                        """\
                        A group of double braces, {{ ... }}, was detected in
                        your string. Manim splits TeX strings at the double
                        braces, which might have caused the current
                        compilation error. If you didn't use the double brace
                        split intentionally, add spaces between the braces to
                        avoid the automatic splitting: {{ ... }} --> { { ... } }.
                        """,
                    ),
                )
            raise compilation_error
        self.set_color_by_tex_to_color_map(self.tex_to_color_map)

        if self.organize_left_to_right:
            self._organize_submobjects_left_to_right()
Esempio n. 4
0
def main():
    args = parse_args(sys.argv)

    if hasattr(args, "cmd"):
        if args.cmd == "cfg":
            if args.subcmd:
                from manim.config import cfg_subcmds

                if args.subcmd == "write":
                    cfg_subcmds.write(args.level, args.open)
                elif args.subcmd == "show":
                    cfg_subcmds.show()
                elif args.subcmd == "export":
                    cfg_subcmds.export(args.dir)
            else:
                logger.error("No subcommand provided; Exiting...")

        # elif args.cmd == "some_other_cmd":
        #     something_else_here()

    else:
        update_config_with_cli(args)
        init_dirs(file_writer_config)

        if file_writer_config["log_to_file"]:
            set_file_logger()

        module = get_module(file_writer_config["input_file"])
        all_scene_classes = get_scene_classes_from_module(module)
        scene_classes_to_render = get_scenes_to_render(all_scene_classes)
        for SceneClass in scene_classes_to_render:
            try:
                if config["use_js_renderer"]:
                    frame_server_impl.get(SceneClass).start()
                else:
                    scene = SceneClass()
                    scene.render()
                    open_file_if_needed(scene.renderer.file_writer)
            except Exception:
                print("\n\n")
                traceback.print_exc()
                print("\n\n")
Esempio n. 5
0
def main():
    args = parse_args(sys.argv)

    if hasattr(args, "cmd"):
        if args.cmd == "cfg":
            if args.subcmd:
                from manim.config import cfg_subcmds

                if args.subcmd == "write":
                    cfg_subcmds.write(args.level, args.open)
                elif args.subcmd == "show":
                    cfg_subcmds.show()
                elif args.subcmd == "export":
                    cfg_subcmds.export(args.dir)
            else:
                logger.error("No subcommand provided; Exiting...")

        # elif args.cmd == "some_other_cmd":
        #     something_else_here()

    else:
        config.digest_args(args)

        module = get_module(config.get_dir("input_file"))
        all_scene_classes = get_scene_classes_from_module(module)
        scene_classes_to_render = get_scenes_to_render(all_scene_classes)
        for SceneClass in scene_classes_to_render:
            try:
                if config["use_js_renderer"]:
                    if frame_server_impl is None:
                        raise ImportError(
                            "Dependencies for JS renderer is not installed.")
                    frame_server_impl.get(SceneClass).start()
                else:
                    scene = SceneClass()
                    scene.render()
                    open_file_if_needed(scene.renderer.file_writer)
            except Exception:
                print("\n\n")
                traceback.print_exc()
                print("\n\n")
Esempio n. 6
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)
Esempio n. 7
0
def tex_to_svg_file_online(expression, environment=None, tex_template=None):
    """Takes a tex expression and returns the path to the svg file of the compiled tex
    after compiling it via one of two online rendering services: LaTeX4Technics or QuickLaTeX

    Parameters
    ----------
    expression : :class:`str`
        String containing the TeX expression to be rendered, e.g. ``\\sqrt{2}`` or ``foo``
    environment : Optional[:class:`str`], optional
        The string containing the environment in which the expression should be typeset, e.g. ``align*``
    tex_template : Optional[:class:`~.TexTemplate`], optional
        Template class used to typesetting. If not set, use default template set via `config["tex_template"]`

    Returns
    -------
    :class:`str`
        Path to generated SVG file.
    """
    if tex_template is None:
        tex_template = config["tex_template"]
    tex_file = generate_tex_file(expression, environment, tex_template)

    # NOTE: quicklatex is a much smaller service than Latex4Technics. As such, it is preferred that
    # quicklatex be used if and only if latex4technics is down.

    hosts = [
        "https://www.latex4technics.com/compileLatex",
        "https://www.quicklatex.com/latex3.f",
    ]

    hostid = (1 if urllib.request.urlopen(
        "https://www.latex4technics.com/").getcode() != 200 else 0)

    if hostid == 0:
        params = {
            "content":
            tex_template.get_texcode_for_expression_in_env(
                expression, environment) if environment is not None else
            tex_template.get_texcode_for_expression(expression),
            "compile_mode":
            "full",  # So we can give them the entire latex file.
            "crop":
            True,
            "format":
            "svg",
            "resolution":
            100,
        }
        payload = params
    elif hostid == 1:
        begin, end = tex_template._texcode_for_environment(environment)
        if environment is not None:
            begin, end = tex_template._texcode_for_environment(environment)
            formula = f"{begin}\n{expression}\n{end}"
        else:
            formula = expression
        params = {
            "formula": formula,
            "preamble": tex_template.preamble,
            "out": 2,  # 2 For SVG output (though the recieved URL is a png...)
        }
        payload = urllib.parse.urlencode(
            params, quote_via=urllib.parse.quote
        )  # This is so requests doesn't use plus signs instead of spaces like it usually does.

    logger.debug(f'Rendering "{expression}" via {hosts[hostid]}...')

    response = requests.post(hosts[hostid], data=payload)

    if hostid == 0:
        responsedict = response.json()
        if responsedict["error"] == True:
            if responsedict["content"].find("! LaTeX Error") > -1:
                relevant_error_start = responsedict["content"].find(
                    "! LaTeX Error")
                error = responsedict["content"][relevant_error_start:]
            else:
                error = responsedict["content"]
            logger.error(error)
        else:
            svgtext = base64.b64decode(responsedict["content"]).decode("utf-8")

    elif hostid == 1:
        if not response.text.startswith("0"):
            error = "\n".join(
                response.text.split("\r\n")[2:]
            )  # First 2 lines are API error code and error image URL resp.
            logger.error(error)
        else:
            svgurl = response.text.split("\n")[-1].split(" ")[0].replace(
                "png", "svg")
            svgtext = requests.get(svgurl,
                                   headers={
                                       "Accept-Encoding": "identity"
                                   }).text

    svgfilepath = tex_file.replace("tex", "svg")
    with open(svgfilepath, "w") as svgfile:
        svgfile.write(svgtext)
    logger.debug(f'SVG of "{expression}" written to {svgfilepath}')

    return svgfilepath