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")
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")
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()
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")
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")
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)
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