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
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"])
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)
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, ), )
def __init__(self, *args, **kwargs): logger.warning( "VMobjectFromSVGPathstring has been deprecated in favour " "of SVGPathMobject. Please use SVGPathMobject instead.") SVGPathMobject.__init__(self, *args, **kwargs)
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)