From a529a59abf1f6af02e664dbad1c8474f3a25a61e Mon Sep 17 00:00:00 2001 From: Aathish Date: Thu, 14 May 2020 11:11:22 +0530 Subject: [PATCH] Added DocStrings for all methods in Container, Scene, GraphScene, MovingCameraScene, SceneFileWriter, ThreeDScene, SpecialThreeDScene, ZoomedScene, VectorScene, and LinearTransformationScene. (#1040) * Added DocStrings for methods in Container and Scene. Removed 2 unused imports from scene.py. * Added DocStrings for all methods in GraphScene, MovingCameraScene, SceneFileWriter, ThreeDScene, SpecialThreeDScene and ZoomedScene. * Added DocStrings for all methods in `VectorScene` and LinearTransformationScene... ...except `position_x_coordinate` and `position_y_coordinate` Co-authored-by: Aathish Sivasubrahmanian --- manimlib/container/container.py | 11 + manimlib/scene/graph_scene.py | 503 ++++++++++++++++++++- manimlib/scene/moving_camera_scene.py | 17 + manimlib/scene/scene.py | 624 +++++++++++++++++++++++++- manimlib/scene/scene_file_writer.py | 195 ++++++++ manimlib/scene/three_d_scene.py | 170 +++++++ manimlib/scene/vector_space_scene.py | 552 ++++++++++++++++++++++- manimlib/scene/zoomed_scene.py | 54 +++ sanim | 1 + 9 files changed, 2111 insertions(+), 16 deletions(-) create mode 160000 sanim diff --git a/manimlib/container/container.py b/manimlib/container/container.py index 5314f2f8..95cf004b 100644 --- a/manimlib/container/container.py +++ b/manimlib/container/container.py @@ -8,13 +8,24 @@ from manimlib.utils.config_ops import digest_config class Container(object): + """ + Base class for Scenes and Mobjects. Generic container. + """ def __init__(self, **kwargs): digest_config(self, kwargs) def add(self, *items): + """ + Generic method to add items to Container. + Must be implemented by subclasses. + """ raise Exception( "Container.add is not implemented; it is up to derived classes to implement") def remove(self, *items): + """ + Generic method to remove items from Container. + Must be implemented by subclasses. + """ raise Exception( "Container.remove is not implemented; it is up to derived classes to implement") diff --git a/manimlib/scene/graph_scene.py b/manimlib/scene/graph_scene.py index 7d5dcf48..03550e39 100644 --- a/manimlib/scene/graph_scene.py +++ b/manimlib/scene/graph_scene.py @@ -54,6 +54,10 @@ class GraphScene(Scene): } def setup(self): + """ + This method is used internally by Manim + to set up the scene for proper use. + """ self.default_graph_colors_cycle = it.cycle(self.default_graph_colors) self.left_T_label = VGroup() @@ -62,6 +66,14 @@ class GraphScene(Scene): self.right_v_line = VGroup() def setup_axes(self, animate=False): + """ + This method sets up the axes of the graph. + + Parameters + ---------- + animate (bool=False) + Whether or not to animate the setting up of the Axes. + """ # TODO, once eoc is done, refactor this to be less redundant. x_num_range = float(self.x_max - self.x_min) self.space_unit_to_x = self.x_axis_width / x_num_range @@ -135,12 +147,51 @@ class GraphScene(Scene): self.default_graph_colors = it.cycle(self.default_graph_colors) def coords_to_point(self, x, y): + """ + The graph is smaller than the scene. + Because of this, coordinates in the scene don't map + to coordinates on the graph. + This method returns a scaled coordinate for the graph, + given cartesian coordinates that correspond to the scene.. + + Parameters + ---------- + x : (int,float) + The x value + + y : (int,float) + The y value + + Returns + ------- + np.ndarray + The array of the coordinates. + """ assert(hasattr(self, "x_axis") and hasattr(self, "y_axis")) result = self.x_axis.number_to_point(x)[0] * RIGHT result += self.y_axis.number_to_point(y)[1] * UP return result def point_to_coords(self, point): + """ + The scene is smaller than the graph. + + Because of this, coordinates in the graph don't map + to coordinates on the scene. + + This method returns a scaled coordinate for the scene, + given coordinates that correspond to the graph. + + Parameters + ---------- + point (np.ndarray) + The point on the graph. + + Returns + ------- + tuple + The coordinates on the scene. + """ return (self.x_axis.point_to_number(point), self.y_axis.point_to_number(point)) @@ -151,6 +202,33 @@ class GraphScene(Scene): x_max=None, **kwargs ): + """ + This method gets a curve to plot on the graph. + + Parameters + ---------- + func : function + The function to plot. It's return value should be + the y-coordinate for a given x-coordinate + + color : str + The string of the RGB color of the curve. in Hexadecimal representation. + + x_min : (Union[int,float]) + The lower x_value from which to plot the curve. + + x_max : (Union[int,float]) + The higher x_value until which to plot the curve. + + **kwargs: + Any valid keyword arguments of ParametricFunction. + + Return + ------ + ParametricFunction + The Parametric Curve for the function passed. + + """ if color is None: color = next(self.default_graph_colors_cycle) if x_min is None: @@ -174,17 +252,99 @@ class GraphScene(Scene): return graph def input_to_graph_point(self, x, graph): + """ + This method returns a coordinate on the curve + given an x_value and a the graoh-curve for which + the corresponding y value should be found. + + Parameters + ---------- + x (Union[int, float]) + The x value for which to find the y value. + + graph ParametricFunction + The ParametricFunction object on which + the x and y value lie. + + Returns + ------- + numpy.nparray + The array of the coordinates on the graph. + """ return self.coords_to_point(x, graph.underlying_function(x)) def angle_of_tangent(self, x, graph, dx=0.01): + """ + Returns the angle to the x axis of the tangent + to the plotted curve at a particular x-value. + + Parameters + ---------- + x (Union[int, float]) + The x value at which the tangent must touch the curve. + + graph ParametricFunction + The ParametricFunction for which to calculate the tangent. + + dx (Union(float, int =0.01)) + The small change in x with which a small change in y + will be compared in order to obtain the tangent. + + Returns + ------- + float + The angle of the tangent with the x axis. + """ vect = self.input_to_graph_point( x + dx, graph) - self.input_to_graph_point(x, graph) return angle_of_vector(vect) def slope_of_tangent(self, *args, **kwargs): + """ + Returns the slople of the tangent to the plotted curve + at a particular x-value. + + Parameters + ---------- + x (Union[int, float]) + The x value at which the tangent must touch the curve. + + graph ParametricFunction + The ParametricFunction for which to calculate the tangent. + + dx (Union(float, int =0.01)) + The small change in x with which a small change in y + will be compared in order to obtain the tangent. + + Returns + ------- + float + The slope of the tangent with the x axis. + """ return np.tan(self.angle_of_tangent(*args, **kwargs)) def get_derivative_graph(self, graph, dx=0.01, **kwargs): + """ + Returns the curve of the derivative of the passed + graph. + + Parameters + ---------- + graph (ParametricFunction) + The graph for which the derivative must be found. + + dx (Union(float, int =0.01)) + The small change in x with which a small change in y + will be compared in order to obtain the derivative. + + **kwargs + Any valid keyword argument of ParametricFunction + + Returns + ------- + ParametricFuncion + The curve of the derivative. + """ if "color" not in kwargs: kwargs["color"] = self.default_derivative_color @@ -201,6 +361,37 @@ class GraphScene(Scene): buff=MED_SMALL_BUFF, color=None, ): + """ + This method returns a properly positioned label for the passed graph, + styled with the passed parameters. + + Parameters + ---------- + graph : ParametricFunction + The curve of the function plotted. + + label : str = "f(x)" + The label for the function's curve. + + x_val : Union[float, int] + The x_value with which the label should be aligned. + + direction : Union[np.ndarray,list,tuple]=RIGHT + The position, relative to the curve that the label will be at. + e.g LEFT, RIGHT + + buff : Union[float, int] + The buffer space between the curve and the label + + color : str + The color of the label. + + Returns + ------- + TexMobject + The LaTeX of the passed 'label' parameter + + """ label = TexMobject(label) color = color or graph.get_color() label.set_color(color) @@ -234,6 +425,61 @@ class GraphScene(Scene): show_signed_area=True, width_scale_factor=1.001 ): + """ + This method returns the VGroup() of the Riemann Rectangles for + a particular curve. + + Parameters + ---------- + graph (ParametricFunction) + The graph whose area needs to be approximated + by the Riemann Rectangles. + + x_min Union[int,float] + The lower bound from which to start adding rectangles + + x_max Union[int,float] + The upper bound where the rectangles stop. + + dx Union[int,float] + The smallest change in x-values that is + considered significant. + + input_sample_type str + Can be any of "left", "right" or "center + + stroke_width : Union[int, float] + The stroke_width of the border of the rectangles. + + stroke_color : str + The string of hex colour of the rectangle's border. + + fill_opacity Union[int, float] + The opacity of the rectangles. + + start_color : str, + The hex starting colour for the rectangles, + this will, if end_color is a different colour, + make a nice gradient. + + end_color : str, + The hex ending colour for the rectangles, + this will, if start_color is a different colour, + make a nice gradient. + + show_signed_area : bool (True) + Whether or not to indicate -ve area if curve dips below + x-axis. + + width_scale_factor : Union[int, float] + How much the width of the rectangles are scaled by when transforming. + + Returns + ------- + VGroup + A VGroup containing the Riemann Rectangles. + + """ x_min = x_min if x_min is not None else self.x_min x_max = x_max if x_max is not None else self.x_max if start_color is None: @@ -279,6 +525,37 @@ class GraphScene(Scene): stroke_width=1, **kwargs ): + """ + This method returns a list of multiple VGroups of Riemann + Rectangles. The inital VGroups are relatively inaccurate, + but the closer you get to the end the more accurate the Riemann + rectangles become + + Parameters + ---------- + graph (ParametricFunction) + The graph whose area needs to be approximated + by the Riemann Rectangles. + + n_iterations, + The number of VGroups of successive accuracy that are needed. + + max_dx Union[int,float] + The maximum change in x between two VGroups of Riemann Rectangles + + power_base Union[int,float=2] + + stroke_width : Union[int, float] + The stroke_width of the border of the rectangles. + + **kwargs + Any valid keyword arguments of get_riemann_rectangles. + + Returns + ------- + list + The list of Riemann Rectangles of increasing accuracy. + """ return [ self.get_riemann_rectangles( graph=graph, @@ -290,6 +567,27 @@ class GraphScene(Scene): ] def get_area(self, graph, t_min, t_max): + """ + Returns a VGroup of Riemann rectangles + sufficiently small enough to visually + approximate the area under the graph passed. + + Parameters + ---------- + graph (ParametricFunction) + The graph/curve for which the area needs to be gotten. + + t_min Union[int, float] + The lower bound of x from which to approximate the area. + + t_max Union[int, float] + The upper bound of x until which the area must be approximated. + + Returns + ------- + VGroup + The VGroup containing the Riemann Rectangles. + """ numerator = max(t_max - t_min, 0.0001) dx = float(numerator) / self.num_rects return self.get_riemann_rectangles( @@ -301,6 +599,23 @@ class GraphScene(Scene): ).set_fill(opacity=self.area_opacity) def transform_between_riemann_rects(self, curr_rects, new_rects, **kwargs): + """ + This method is used to transform between two VGroups of Riemann Rectangles, + if they were obtained by get_riemann_rectangles or get_riemann_rectangles_list. + No animation is returned, and the animation is directly played. + + Parameters + ---------- + curr_rects : VGroup + The current Riemann Rectangles + + new_rects : VGroup + The Riemann Rectangles to transform to. + + **kwargs + added_anims + Any other animations to play simultaneously. + """ transform_kwargs = { "run_time": 2, "lag_ratio": 0.5 @@ -326,6 +641,31 @@ class GraphScene(Scene): line_class=Line, **line_kwargs ): + """ + This method returns a Vertical line from the x-axis to + the corresponding point on the graph/curve. + + Parameters + ---------- + x Union[int,float] + The x-value at which the line should be placed/calculated. + + graph (ParametricFunction) + The graph on which the line should extend to. + + line_class (Line and similar) + The type of line that should be used. + Defaults to Line + + **line_kwargs + Any valid keyword arguments of the object passed in "line_class" + If line_class is Line, any valid keyword arguments of Line are allowed. + + Return + ------ + An object of type passed in "line_class" + Defaults to Line + """ if "color" not in line_kwargs: line_kwargs["color"] = graph.get_color() return line_class( @@ -341,6 +681,30 @@ class GraphScene(Scene): num_lines=20, **kwargs ): + """ + Obtains multiple lines from the x axis to the Graph/curve. + + Parameters + ---------- + graph (ParametricFunction) + The graph on which the line should extend to. + + x_min (Union[int, float]) + The lower bound from which lines can appear. + + x_max (Union[int, float]) + The upper bound until which the lines can appear. + + num_lines (Union[int, float]) + The number of lines (evenly spaced) + that are needed. + + Returns + ------- + VGroup + The VGroup of the evenly spaced lines. + + """ x_min = x_min or self.x_min x_max = x_max or self.x_max return VGroup(*[ @@ -361,14 +725,57 @@ class GraphScene(Scene): secant_line_length=10, ): """ - Resulting group is of the form VGroup( - dx_line, - df_line, - dx_label, (if applicable) - df_label, (if applicable) - secant_line, (if applicable) - ) - with attributes of those names. + This method returns a VGroup of (two lines + representing dx and df, the labels for dx and + df, and the Secant to the Graph/curve at a + particular x value. + + Parameters + ---------- + x (Union[float, int]) + The x value at which the secant enters, and intersects + the graph for the first time. + + graph (ParametricFunction) + The curve/graph for which the secant must + be found. + + dx (Union[float, int]) + The change in x after which the secant exits. + + dx_line_color (str) + The line color for the line that indicates the change in x. + + df_line_color (str) + The line color for the line that indicates the change in y. + + dx_label (str) + The label to be provided for the change in x. + + df_label (str) + The label to be provided for the change in y. + + include_secant_line (bool=True) + Whether or not to include the secant line in the graph, + or just have the df and dx lines and labels. + + secant_line_color (str) + The color of the secant line. + + secant_line_length (Union[float,int=10]) + How long the secant line should be. + + Returns: + -------- + VGroup + Resulting group is of the form VGroup( + dx_line, + df_line, + dx_label, (if applicable) + df_label, (if applicable) + secant_line, (if applicable) + ) + with attributes of those names. """ kwargs = locals() kwargs.pop("self") @@ -438,6 +845,33 @@ class GraphScene(Scene): return group def add_T_label(self, x_val, side=RIGHT, label=None, color=WHITE, animated=False, **kwargs): + """ + This method adds to the Scene: + -- a Vertical line from the x-axis to the corresponding point on the graph/curve. + -- a small vertical Triangle whose top point lies on the base of the vertical line + -- a TexMobject to be a label for the Line and Triangle, at the bottom of the Triangle. + The scene needs to have the graph have the identifier/variable name self.v_graph. + + Parameters + ---------- + x_val (Union[float, int]) + The x value at which the secant enters, and intersects + the graph for the first time. + + side (np.ndarray()) + + label (str) + The label to give the vertline and triangle + + color (str) + The hex color of the label. + + animated (bool=False) + Whether or not to animate the addition of the T_label + + **kwargs + Any valid keyword argument of a self.play call. + """ triangle = RegularPolygon(n=3, start_angle=np.pi / 2) triangle.set_height(MED_SMALL_BUFF) triangle.move_to(self.coords_to_point(x_val, 0), UP) @@ -479,6 +913,33 @@ class GraphScene(Scene): fade_close_to_origin=True, run_time=1.0 ): + """ + This method requires a lot of prerequisites: + self.area must be defined from self.get_area() + self.left_v_line and self.right_v_line must be defined from self.get_v_line + self.left_T_label_group and self.right_T_label_group must be defined from self.add_T_label + + This method will returna VGroup of new mobjects for each of those, when provided the graph/curve, + the new t_min and t_max, the run_time and a bool stating whether or not to fade when close to + the origin. + + Parameters + ---------- + graph (ParametricFunction) + The graph for which this must be done. + + new_t_min (Union[int,float]) + The new lower bound. + + new_t_max (Union[int,float]) + The new upper bound. + + fade_close_to_origin (bool=True) + Whether or not to fade when close to the origin. + + run_time (Union[int,float=1.0]) + The run_time of the animation of this change. + """ curr_t_min = self.x_axis.point_to_number(self.area.get_left()) curr_t_max = self.x_axis.point_to_number(self.area.get_right()) if new_t_min is None: @@ -532,6 +993,32 @@ class GraphScene(Scene): added_anims=None, **anim_kwargs ): + """ + This method animates the change of the secant slope group from + the old secant slope group, into a new secant slope group. + + Parameters + ---------- + secant_slope_group (VGroup) + The old secant_slope_group + + target_dx Union[int, float] + The new dx value. + + target_x Union[int, float] + The new x value at which the secant should be. + + run_time Union[int,float=3] + The run time for this change when animated. + + added_anims + Any exta animations that should be played alongside. + + **anim_kwargs + Any valid kwargs of a self.play call. + + NOTE: At least one of target_dx and target_x should be not None. + """ if target_dx is None and target_x is None: raise Exception( "At least one of target_x and target_dx must not be None") diff --git a/manimlib/scene/moving_camera_scene.py b/manimlib/scene/moving_camera_scene.py index 4fa23268..3d419e19 100644 --- a/manimlib/scene/moving_camera_scene.py +++ b/manimlib/scene/moving_camera_scene.py @@ -4,11 +4,19 @@ from manimlib.utils.iterables import list_update class MovingCameraScene(Scene): + """ + This is a Scene, with special configurations and properties that + make it suitable for cases where the camera must be moved around. + """ CONFIG = { "camera_class": MovingCamera } def setup(self): + """ + This method is used internally by Manim + to set up the scene for proper use. + """ Scene.setup(self) assert(isinstance(self.camera, MovingCamera)) self.camera_frame = self.camera.frame @@ -17,6 +25,15 @@ class MovingCameraScene(Scene): return self def get_moving_mobjects(self, *animations): + """ + This method returns a list of all of the Mobjects in the Scene that + are moving, that are also in the animations passed. + + Parameters + ---------- + *animations (Animation) + The animations whose mobjects will be checked. + """ moving_mobjects = Scene.get_moving_mobjects(self, *animations) all_moving_mobjects = self.camera.extract_mobject_family_members( moving_mobjects diff --git a/manimlib/scene/scene.py b/manimlib/scene/scene.py index e187b2c1..5b408ba5 100644 --- a/manimlib/scene/scene.py +++ b/manimlib/scene/scene.py @@ -7,18 +7,40 @@ from tqdm import tqdm as ProgressDisplay import numpy as np from manimlib.animation.animation import Animation -from manimlib.animation.creation import Write from manimlib.animation.transform import MoveToTarget, ApplyMethod from manimlib.camera.camera import Camera from manimlib.constants import * from manimlib.container.container import Container from manimlib.mobject.mobject import Mobject -from manimlib.mobject.svg.tex_mobject import TextMobject from manimlib.scene.scene_file_writer import SceneFileWriter from manimlib.utils.iterables import list_update class Scene(Container): + """ + A Scene can be thought of as the Canvas of your animation. + All of your own named Scenes will be subclasses of this Scene, or + other named scenes. + + Use a construct() function to tell Manim what should go on in the Scene. + + E.G: + + class MyScene(Scene): + def construct(self): + self.play( + Write(Text("Hello World!")) + ) + + Some important variables to note are: + camera: The camera object to be used for the scene. + file_writer : The object that writes the animations in the scene to a video file. + mobjects : The list of mobjects present in the scene. + foreground_mobjects : List of mobjects explicitly in the foreground. + num_plays : Number of play() functions in the scene. + time: time elapsed since initialisation of scene. + random_seed: The seed with which all random operations are done. + """ CONFIG = { "camera_class": Camera, "camera_config": {}, @@ -59,22 +81,35 @@ class Scene(Container): def setup(self): """ - This is meant to be implement by any scenes which + This is meant to be implemented by any scenes which are comonly subclassed, and have some common setup involved before the construct method is called. """ pass def tear_down(self): + """ + This is meant to be implemented by any scenes which + are comonly subclassed, and have some common method + to be invoked before the scene ends. + """ pass def construct(self): + """ + The primary method for constructing (i.e adding content to) + the Scene. + """ pass # To be implemented in subclasses def __str__(self): return self.__class__.__name__ def print_end_message(self): + """ + Used internally to print the number of + animations played after the scene ends. + """ print("Played {} animations".format(self.num_plays)) def set_variables_as_attrs(self, *objects, **newly_named_objects): @@ -93,37 +128,111 @@ class Scene(Container): return self def get_attrs(self, *keys): + """ + Gets attributes of a scene given the attribute's identifier/name. + + Parameters + ---------- + *args: (str) + Name(s) of the argument(s) to return the attribute of. + + Returns + ------- + list + List of attributes of the passed identifiers. + """ return [getattr(self, key) for key in keys] # Only these methods should touch the camera def set_camera(self, camera): + """ + Sets the scene's camera to be the passed Camera Object. + Parameters + ---------- + camera: Union[Camera, MappingCamera,MovingCamera,MultiCamera,ThreeDCamera] + Camera object to use. + """ self.camera = camera def get_frame(self): + """ + Gets current frame as NumPy array. + + Returns + ------- + np.array + NumPy array of pixel values of each pixel in screen + """ return np.array(self.camera.get_pixel_array()) def get_image(self): + """ + Gets current frame as PIL Image + + Returns + ------- + PIL.Image + PIL Image object of current frame. + """ return self.camera.get_image() def set_camera_pixel_array(self, pixel_array): + """ + Sets the camera to display a Pixel Array + + Parameters + ---------- + pixel_array: Union[np.ndarray,list,tuple] + Pixel array to set the camera to display + """ self.camera.set_pixel_array(pixel_array) def set_camera_background(self, background): + """ + Sets the camera to display a Pixel Array + + Parameters + ---------- + background: Union[np.ndarray,list,tuple] + + """ self.camera.set_background(background) def reset_camera(self): + """ + Resets the Camera to its original configuration. + """ self.camera.reset() - def capture_mobjects_in_camera(self, mobjects, **kwargs): + def capture_mobjects_in_camera(self, mobjects, **kwargs): #TODO Add more detail to docstring. + """ + This method is used internally. + """ self.camera.capture_mobjects(mobjects, **kwargs) - def update_frame( + def update_frame( #TODO Description in Docstring self, mobjects=None, background=None, include_submobjects=True, ignore_skipping=True, **kwargs): + """ + Parameters: + ----------- + mobjects: list + list of mobjects + + background: np.ndarray + Pixel Array for Background + + include_submobjects: bool (True) + + ignore_skipping : bool (True) + + **kwargs + + """ if self.skip_animations and not ignore_skipping: return if mobjects is None: @@ -146,10 +255,26 @@ class Scene(Container): ### def update_mobjects(self, dt): + """ + Begins updating all mobjects in the Scene. + + Parameters + ---------- + dt: Union[int,float] + Change in time between updates. Defaults (mostly) to 1/frames_per_second + """ for mobject in self.mobjects: mobject.update(dt) def should_update_mobjects(self): + """ + Returns True if any mobject in Scene is being updated + or if the scene has always_update_mobjects set to true. + + Returns + ------- + bool + """ return self.always_update_mobjects or any([ mob.has_time_based_updater() for mob in self.get_mobject_family_members() @@ -158,14 +283,39 @@ class Scene(Container): ### def get_time(self): + """ + Returns time in seconds elapsed after initialisation of scene + + Returns + ------- + self.time : Union[int,float] + Returns time in seconds elapsed after initialisation of scene + """ return self.time def increment_time(self, d_time): + """ + Increments the time elapsed after intialisation of scene by + passed "d_time". + + Parameters + ---------- + d_time : Union[int,float] + Time in seconds to increment by. + """ self.time += d_time ### def get_top_level_mobjects(self): + """ + Returns all mobjects which are not submobjects. + + Returns + ------- + list + List of top level mobjects. + """ # Return only those which are not in the family # of another mobject from the scene mobjects = self.get_mobjects() @@ -180,12 +330,34 @@ class Scene(Container): return list(filter(is_top_level, mobjects)) def get_mobject_family_members(self): + """ + Returns list of family-members of all mobjects in scene. + If a Circle() and a VGroup(Rectangle(),Triangle()) were added, + it returns not only the Circle(), Rectangle() and Triangle(), but + also the VGroup() object. + + Returns + ------- + list + List of mobject family members. + """ return self.camera.extract_mobject_family_members(self.mobjects) def add(self, *mobjects): """ Mobjects will be displayed, from background to foreground in the order with which they are added. + + Parameters + --------- + *mobjects + Mobjects to add. + + Returns + ------- + Scene + The same scene after adding the Mobjects in. + """ mobjects = [*mobjects, *self.foreground_mobjects] self.restructure_mobjects(to_remove=mobjects) @@ -205,6 +377,11 @@ class Scene(Container): return self def remove(self, *mobjects): + """ + Removes mobjects in the passed list of mobjects + from the scene and the foreground, by removing them + from "mobjects" and "foreground_mobjects" + """ for list_name in "mobjects", "foreground_mobjects": self.restructure_mobjects(mobjects, list_name, False) return self @@ -213,10 +390,31 @@ class Scene(Container): mobject_list_name="mobjects", extract_families=True): """ + tl:wr + If your scene has a Group(), and you removed a mobject from the Group, + this dissolves the group and puts the rest of the mobjects directly + in self.mobjects or self.foreground_mobjects. + In cases where the scene contains a group, e.g. Group(m1, m2, m3), but one of its submobjects is removed, e.g. scene.remove(m1), the list of mobjects - will be editing to contain other submobjects, but not m1, e.g. it will now + will be edited to contain other submobjects, but not m1, e.g. it will now insert m2 and m3 to where the group once was. + + Parameters + ---------- + to_remove : Mobject + The Mobject to remove. + + mobject_list_name : str + The list of mobjects ("mobjects", "foreground_mobjects" etc) to remove from. + + extract_families : bool + Whether the mobject's families should be recursively extracted. + + Returns + ------- + Scene + The Scene mobject with restructured Mobjects. """ if extract_families: to_remove = self.camera.extract_mobject_family_members(to_remove) @@ -226,6 +424,25 @@ class Scene(Container): return self def get_restructured_mobject_list(self, mobjects, to_remove): + """ + Given a list of mobjects and a list of mobjects to be removed, this + filters out the removable mobjects from the list of mobjects. + + Parameters + ---------- + + mobjects : list + The Mobjects to check. + + to_remove : list + The list of mobjects to remove. + + Returns + ------- + list + The list of mobjects with the mobjects to remove removed. + """ + new_mobjects = [] def add_safe_mobjects_from_list(list_to_examine, set_to_remove): @@ -242,6 +459,20 @@ class Scene(Container): # TODO, remove this, and calls to this def add_foreground_mobjects(self, *mobjects): + """ + Adds mobjects to the foreground, and internally to the list + foreground_mobjects, and mobjects. + + Parameters + ---------- + *mobjects : Mobject + The Mobjects to add to the foreground. + + Returns + ------ + Scene + The Scene, with the foreground mobjects added. + """ self.foreground_mobjects = list_update( self.foreground_mobjects, mobjects @@ -250,36 +481,151 @@ class Scene(Container): return self def add_foreground_mobject(self, mobject): + """ + Adds a single mobject to the foreground, and internally to the list + foreground_mobjects, and mobjects. + + Parameters + ---------- + mobject : Mobject + The Mobject to add to the foreground. + + Returns + ------ + Scene + The Scene, with the foreground mobject added. + """ return self.add_foreground_mobjects(mobject) def remove_foreground_mobjects(self, *to_remove): + """ + Removes mobjects from the foreground, and internally from the list + foreground_mobjects. + + Parameters + ---------- + *to_remove : Mobject + The mobject(s) to remove from the foreground. + + Returns + ------ + Scene + The Scene, with the foreground mobjects removed. + """ self.restructure_mobjects(to_remove, "foreground_mobjects") return self def remove_foreground_mobject(self, mobject): + """ + Removes a single mobject from the foreground, and internally from the list + foreground_mobjects. + + Parameters + ---------- + mobject : Mobject + The mobject to remove from the foreground. + + Returns + ------ + Scene + The Scene, with the foreground mobject removed. + """ return self.remove_foreground_mobjects(mobject) def bring_to_front(self, *mobjects): + """ + Adds the passed mobjects to the scene again, + pushing them to he front of the scene. + + Parameters + ---------- + *mobjects : Mobject + The mobject(s) to bring to the front of the scene. + + Returns + ------ + Scene + The Scene, with the mobjects brought to the front + of the scene. + """ self.add(*mobjects) return self def bring_to_back(self, *mobjects): + """ + Removes the mobject from the scene and + adds them to the back of the scene. + + Parameters + ---------- + *mobjects : Mobject + The mobject(s) to push to the back of the scene. + + Returns + ------ + Scene + The Scene, with the mobjects pushed to the back + of the scene. + """ self.remove(*mobjects) self.mobjects = list(mobjects) + self.mobjects return self def clear(self): + """ + Removes all mobjects present in self.mobjects + and self.foreground_mobjects from the scene. + + Returns + ------ + Scene + The Scene, with all of its mobjects in + self.mobjects and self.foreground_mobjects + removed. + """ self.mobjects = [] self.foreground_mobjects = [] return self def get_mobjects(self): + """ + Returns all the mobjects in self.mobjects + + Returns + ------ + list + The list of self.mobjects . + """ return list(self.mobjects) def get_mobject_copies(self): + """ + Returns a copy of all mobjects present in + self.mobjects . + + Returns + ------ + list + A list of the copies of all the mobjects + in self.mobjects + """ return [m.copy() for m in self.mobjects] def get_moving_mobjects(self, *animations): + """ + Gets all moving mobjects in the passed animation(s). + + Parameters + ---------- + *animations + The animations to check for moving mobjects. + + Returns + ------ + list + The list of mobjects that could be moving in + the Animation(s) + """ # Go through mobjects from start to end, and # as soon as there's one that needs updating of # some kind per frame, return the list from that @@ -297,6 +643,32 @@ class Scene(Container): return [] def get_time_progression(self, run_time, n_iterations=None, override_skip_animations=False): + """ + You will hardly use this when making your own animations. + This method is for Manim's internal use. + + Returns a CommandLine ProgressBar whose fill_time + is dependent on the run_time of an animation, + the iterations to perform in that animation + and a bool saying whether or not to consider + the skipped animations. + + Parameters + ---------- + run_time: Union[int,float] + The run_time of the animation. + + n_iterations: None, int + The number of iterations in the animation. + + override_skip_animations: bool (True) + Whether or not to show skipped animations in the progress bar. + + Returns + ------ + ProgressDisplay + The CommandLine Progress Bar. + """ if self.skip_animations and not override_skip_animations: times = [run_time] else: @@ -310,9 +682,43 @@ class Scene(Container): return time_progression def get_run_time(self, animations): + """ + Gets the total run time for a list of animations. + + Parameters + ---------- + animations: list + A list of the animations whose total + run_time is to be calculated. + + Returns + ------ + float + The total run_time of all of the animations in the list. + """ + return np.max([animation.run_time for animation in animations]) def get_animation_time_progression(self, animations): + """ + You will hardly use this when making your own animations. + This method is for Manim's internal use. + + Uses get_time_progression to obtaina + CommandLine ProgressBar whose fill_time is + dependent on the qualities of the passed animation, + + Parameters + ---------- + animations : list + The list of animations to get + the time progression for. + + Returns + ------ + ProgressDisplay + The CommandLine Progress Bar. + """ run_time = self.get_run_time(animations) time_progression = self.get_time_progression(run_time) time_progression.set_description("".join([ @@ -332,6 +738,15 @@ class Scene(Container): s hit, a MoveToTarget animation is built using the args that follow up until either another animation is hit, another method is hit, or the args list runs out. + + Parameters + ---------- + *args : Union[Animation, method(of a mobject, which is followed by that method's arguments)] + **kwargs : any named arguments like run_time or lag_ratio. + + Returns + ------- + list : list of animations with the parameters applied to them. """ animations = [] state = { @@ -390,6 +805,14 @@ class Scene(Container): return animations def update_skipping_status(self): + """ + This method is used internally to check if the current + animation needs to be skipped or not. It also checks if + the number of animations that were played correspond to + the number of animations that need to be played, and + raises an EndSceneEarlyException if they don't correspond. + """ + if self.start_at_animation_number: if self.num_plays == self.start_at_animation_number: self.skip_animations = False @@ -399,6 +822,25 @@ class Scene(Container): raise EndSceneEarlyException() def handle_play_like_call(func): + """ + This method is used internally to wrap the + passed function, into a function that + actually writes to the video stream. + Simultaneously, it also adds to the number + of animations played. + + Parameters + ---------- + func: function object + The play() like function that has to be + written to the video file stream. + + Returns + ------- + function object + The play() like function that can now write + to the video file stream. + """ def wrapper(self, *args, **kwargs): self.update_skipping_status() allow_write = not self.skip_animations @@ -409,6 +851,17 @@ class Scene(Container): return wrapper def begin_animations(self, animations): + """ + This method begins the list of animations that is passed, + and adds any mobjects involved (if not already present) + to the scene again. + + Parameters + ---------- + animations: list + List of involved animations. + + """ curr_mobjects = self.get_mobject_family_members() for animation in animations: # Begin animation @@ -421,6 +874,15 @@ class Scene(Container): curr_mobjects += mob.get_family() def progress_through_animations(self, animations): + """ + This method progresses through each animation + in the list passed and and updates the frames as required. + + Parameters + ---------- + animations: list + List of involved animations. + """ # Paint all non-moving objects onto the screen, so they don't # have to be rendered every frame moving_mobjects = self.get_moving_mobjects(*animations) @@ -439,6 +901,15 @@ class Scene(Container): self.add_frames(self.get_frame()) def finish_animations(self, animations): + """ + This function cleans up after the end + of each animation in the passed list. + + Parameters + ---------- + animations: list + list of animations to finish. + """ for animation in animations: animation.finish() animation.clean_up_from_scene(self) @@ -453,6 +924,16 @@ class Scene(Container): @handle_play_like_call def play(self, *args, **kwargs): + """ + This method is used to prep the animations for rendering, + apply the arguments and parameters required to them, + render them, and write them to the video file. + + Parameters + ---------- + *args: Animation, mobject with mobject method and params + **kwargs: named parameters affecting what was passed in *args e.g run_time, lag_ratio etc. + """ if len(args) == 0: warnings.warn("Called Scene.play with no animations") return @@ -464,19 +945,70 @@ class Scene(Container): self.finish_animations(animations) def idle_stream(self): + """ + This method is used internally to + idle the vide file_writer until an + animation etc needs to be written + to the video file. + """ self.file_writer.idle_stream() def clean_up_animations(self, *animations): + """ + This method cleans up and removes from the + scene all the animations that were passed + + Parameters + ---------- + *animations: Animation + Animation to clean up. + + Returns + ------- + Scene + The scene with the animations + cleaned up. + + """ for animation in animations: animation.clean_up_from_scene(self) return self def get_mobjects_from_last_animation(self): + """ + This method returns the mobjects from the previous + played animation, if any exist, and returns an empty + list if not. + + Returns + -------- + list + The list of mobjects from the previous animation. + + """ if hasattr(self, "mobjects_from_last_animation"): return self.mobjects_from_last_animation return [] def get_wait_time_progression(self, duration, stop_condition): + """ + This method is used internally to obtain the CommandLine + Progressbar for when self.wait() is called in a scene. + + Parameters + ---------- + duration: Union[list,float] + duration of wait time + + stop_condition: function + The function which determines whether to continue waiting. + + Returns + ------- + ProgressBar + The CommandLine ProgressBar of the wait time + + """ if stop_condition is not None: time_progression = self.get_time_progression( duration, @@ -495,6 +1027,23 @@ class Scene(Container): @handle_play_like_call def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None): + """ + This method is used to wait, and do nothing to the scene, for some + duration. + Updaters stop updating, nothing happens. + + Parameters + ---------- + duration : Union[float, int] + The duration of wait time. Defaults to None. + stop_condition : + A function that determines whether to stop waiting or not. + + Returns + ------- + Scene + The scene, after waiting. + """ self.update_mobjects(dt=0) # Any problems with this? if self.should_update_mobjects(): time_progression = self.get_wait_time_progression(duration, stop_condition) @@ -522,19 +1071,62 @@ class Scene(Container): return self def wait_until(self, stop_condition, max_time=60): + """ + Like a wrapper for wait(). + You pass a function that determines whether to continue waiting, + and a max wait time if that is never fulfilled. + + Parameters + ---------- + stop_condition: function definition + The function whose boolean return value determines whether to continue waiting + + max_time: Union[int,float] + The maximum wait time in seconds, if the stop_condition is never fulfilled. + Defaults to 60. + """ self.wait(max_time, stop_condition=stop_condition) def force_skipping(self): + """ + This forces the skipping of animations, + by setting original_skipping_status to + whatever skip_animations was, and setting + skip_animations to True. + + Returns + ------- + Scene + The Scene, with skipping turned on. + """ self.original_skipping_status = self.skip_animations self.skip_animations = True return self def revert_to_original_skipping_status(self): + """ + Forces the scene to go back to its original skipping status, + by setting skip_animations to whatever it reads + from original_skipping_status. + + Returns + ------- + Scene + The Scene, with the original skipping status. + """ if hasattr(self, "original_skipping_status"): self.skip_animations = self.original_skipping_status return self def add_frames(self, *frames): + """ + Adds a frame to the video_file_stream + + Parameters + ---------- + *frames : numpy.ndarray + The frames to add, as pixel arrays. + """ dt = 1 / self.camera.frame_rate self.increment_time(len(frames) * dt) if self.skip_animations: @@ -543,12 +1135,32 @@ class Scene(Container): self.file_writer.write_frame(frame) def add_sound(self, sound_file, time_offset=0, gain=None, **kwargs): + """ + This method is used to add a sound to the animation. + + Parameters + ---------- + sound_file: str + The path to the sound file. + + time_offset: int,float = 0 + The offset in the sound file after which + the sound can be played. + gain: + + **kwargs : Present for excess? + + """ if self.skip_animations: return time = self.get_time() + time_offset self.file_writer.add_sound(sound_file, time, gain, **kwargs) def show_frame(self): + """ + Opens the current frame in the Default Image Viewer + of your system. + """ self.update_frame(ignore_skipping=True) self.get_image().show() diff --git a/manimlib/scene/scene_file_writer.py b/manimlib/scene/scene_file_writer.py index 80b11108..29833974 100644 --- a/manimlib/scene/scene_file_writer.py +++ b/manimlib/scene/scene_file_writer.py @@ -20,6 +20,21 @@ from manimlib.utils.sounds import get_full_sound_file_path class SceneFileWriter(object): + """ + SceneFileWriter is the object that actually writes the animations + played, into video files, using FFMPEG, and Sox, if sound is needed. + This is mostly for Manim's internal use. You will rarely, if ever, + have to use the methods for this class, unless tinkering with the very + fabric of Manim's reality. + + Some useful attributes are: + "write_to_movie" (bool=False) + Whether or not to write the animations into a video file. + "png_mode" (str="RGBA") + The PIL image mode to use when outputting PNGs + "movie_file_extension" (str=".mp4") + The file-type extension of the outputted video. + """ CONFIG = { "write_to_movie": False, # TODO, save_pngs is doing nothing @@ -44,6 +59,11 @@ class SceneFileWriter(object): # Output directories and files def init_output_directories(self): + """ + This method initialises the directories to which video + files will be written to and read from (within MEDIA_DIR). + If they don't already exist, they will be created. + """ module_directory = self.output_directory or self.get_default_module_directory() scene_name = self.file_name or self.get_default_scene_name() if self.save_last_frame: @@ -90,17 +110,58 @@ class SceneFileWriter(object): )) def get_default_module_directory(self): + """ + This method gets the name of the directory containing + the file that has the Scene that is being rendered. + + Returns + ------- + str + The name of the directory. + """ filename = os.path.basename(self.input_file_path) root, _ = os.path.splitext(filename) return root def get_default_scene_name(self): + """ + This method returns the default scene name + which is the value of "file_name", if it exists and + the actual name of the class that inherited from + Scene in your animation script, if "file_name" is None. + + Returns + ------- + str + The default scene name. + """ if self.file_name is None: return self.scene.__class__.__name__ else: return self.file_name def get_resolution_directory(self): + """ + This method gets the name of the directory that immediately contains the + video file. This name is p + E.G: + If you are rendering an 854x480 px animation at 15fps, the name of the directory + that immediately contains the video file will be + 480p15. + The file structure should look something like: + + MEDIA_DIR + |--Tex + |--texts + |--videos + |-- + |--p + |--.mp4 + Returns + ------- + str + The name of the directory. + """ pixel_height = self.scene.camera.pixel_height frame_rate = self.scene.camera.frame_rate return "{}p{}".format( @@ -109,9 +170,32 @@ class SceneFileWriter(object): # Directory getters def get_image_file_path(self): + """ + This returns the directory path to which any images will be + written to. + It is usually named "images", but can be changed by changing + "image_file_path". + + Returns + ------- + str + The path of the directory. + """ return self.image_file_path def get_next_partial_movie_path(self): + """ + Manim renders each play-like call in a short partial + video file. All such files are then concatenated with + the help of FFMPEG. + + This method returns the path of the next partial movie. + + Returns + ------- + str + The path of the next partial movie. + """ result = os.path.join( self.partial_movie_directory, "{:05}{}".format( @@ -122,18 +206,46 @@ class SceneFileWriter(object): return result def get_movie_file_path(self): + """ + Returns the final path of the written video file. + + Returns + ------- + str + The path of the movie file. + """ return self.movie_file_path # Sound def init_audio(self): + """ + Preps the writer for adding audio to the movie. + """ self.includes_sound = False def create_audio_segment(self): + """ + Creates an empty, silent, Audio Segment. + """ self.audio_segment = AudioSegment.silent() def add_audio_segment(self, new_segment, time=None, gain_to_background=None): + """ + This method adds an audio segment from an + AudioSegment type object and suitable parameters. + + Parameters + ---------- + new_segment (AudioSegment) + The audio segment to add + time (Union[int, float]) + the timestamp at which the + sound should be added. + gain_to_background + The gain of the segment from the background. + """ if not self.includes_sound: self.includes_sound = True self.create_audio_segment() @@ -158,6 +270,25 @@ class SceneFileWriter(object): ) def add_sound(self, sound_file, time=None, gain=None, **kwargs): + """ + This method adds an audio segment from a sound file. + + Parameters + ---------- + sound_file (str) + The path to the sound file. + + time (Union[float, int]) + The timestamp at which the audio should be added. + + gain + The gain of the given audio segment. + + **kwargs + This method uses add_audio_segment, so any keyword arguments + used there can be referenced here. + + """ file_path = get_full_sound_file_path(sound_file) new_segment = AudioSegment.from_file(file_path) if gain: @@ -166,23 +297,62 @@ class SceneFileWriter(object): # Writers def begin_animation(self, allow_write=False): + """ + Used internally by manim to stream the animation to FFMPEG for + displaying or writing to a file. + + Parameters + ---------- + allow_write (bool=False) + Whether or not to write to a video file. + """ if self.write_to_movie and allow_write: self.open_movie_pipe() def end_animation(self, allow_write=False): + """ + Internally used by Manim to stop streaming to + FFMPEG gracefully. + + Parameters + ---------- + allow_write (bool=False) + Whether or not to write to a video file. + """ if self.write_to_movie and allow_write: self.close_movie_pipe() def write_frame(self, frame): + """ + Used internally by Manim to write a frame to + the FFMPEG input buffer. + + Parameters + ---------- + frame (np.ndarray) + Pixel array of the frame. + """ if self.write_to_movie: self.writing_process.stdin.write(frame.tostring()) def save_final_image(self, image): + """ + The name is a misnomer. This method saves the image + passed to it as an in the default image directory. + + Parameters + ---------- + image (np.ndarray) + The pixel array of the image to save. + """ file_path = self.get_image_file_path() image.save(file_path) self.print_file_ready_message(file_path) def idle_stream(self): + """ + Doesn't write anything to the FFMPEG frame buffer. + """ while self.stream_lock: a = datetime.datetime.now() self.update_frame() @@ -196,6 +366,13 @@ class SceneFileWriter(object): sleep(frame_duration - time_diff) def finish(self): + """ + Finishes writing to the FFMPEG buffer. + Combines the partial movie files into the + whole scene. + If save_last_frame is True, saves the last + frame in the default image directory. + """ if self.write_to_movie: if hasattr(self, "writing_process"): self.writing_process.terminate() @@ -205,6 +382,11 @@ class SceneFileWriter(object): self.save_final_image(self.scene.get_image()) def open_movie_pipe(self): + """ + Used internally by Manim to initalise + FFMPEG and begin writing to FFMPEG's input + buffer. + """ file_path = self.get_next_partial_movie_path() temp_file_path = os.path.splitext(file_path)[0] + '_temp' + self.movie_file_extension @@ -243,6 +425,11 @@ class SceneFileWriter(object): self.writing_process = subprocess.Popen(command, stdin=subprocess.PIPE) def close_movie_pipe(self): + """ + Used internally by Manim to gracefully stop writing to FFMPEG's + input buffer, and move the temporary files into their permananant + locations + """ self.writing_process.stdin.close() self.writing_process.wait() shutil.move( @@ -251,6 +438,11 @@ class SceneFileWriter(object): ) def combine_movie_files(self): + """ + Used internally by Manim to combine the separate + partial movie files that make up a Scene into a single + video file for that Scene. + """ # Manim renders the scene as many smaller movie files # which are then concatenated to a larger one. The reason # for this is that sometimes video-editing is made easier when @@ -339,4 +531,7 @@ class SceneFileWriter(object): self.print_file_ready_message(movie_file_path) def print_file_ready_message(self, file_path): + """ + Prints the "File Ready" message to STDOUT. + """ print("\nFile ready at {}\n".format(file_path)) diff --git a/manimlib/scene/three_d_scene.py b/manimlib/scene/three_d_scene.py index 5c2be57f..74d67a72 100644 --- a/manimlib/scene/three_d_scene.py +++ b/manimlib/scene/three_d_scene.py @@ -13,6 +13,10 @@ from manimlib.utils.config_ops import merge_dicts_recursively class ThreeDScene(Scene): + """ + This is a Scene, with special configurations and properties that + make it suitable for Three Dimensional Scenes. + """ CONFIG = { "camera_class": ThreeDCamera, "ambient_camera_rotation": None, @@ -23,6 +27,23 @@ class ThreeDScene(Scene): } def set_camera_orientation(self, phi=None, theta=None, distance=None, gamma=None): + """ + This method sets the orientation of the camera in the scene. + + Parameters + ---------- + phi : (int,float) + The polar angle i.e the angle between Z_AXIS and Camera through ORIGIN in radians. + + theta : (int,float) + The azimuthal angle i.e the angle that spins the camera around the Z_AXIS. + + distance : (int, float) + The radial distance between ORIGIN and Camera. + + gamma : (int, float) + The rotation of the camera about the vector from the ORIGIN to the Camera. + """ if phi is not None: self.camera.set_phi(phi) if theta is not None: @@ -33,6 +54,16 @@ class ThreeDScene(Scene): self.camera.set_gamma(gamma) def begin_ambient_camera_rotation(self, rate=0.02): + """ + This method begins an ambient rotation of the camera about the Z_AXIS, + in the anticlockwise direction + + Parameters + ---------- + rate : (int,float=0.02) + The rate at which the camera should rotate about the Z_AXIS. + Negative rate means clockwise rotation. + """ # TODO, use a ValueTracker for rate, so that it # can begin and end smoothly self.camera.theta_tracker.add_updater( @@ -41,6 +72,9 @@ class ThreeDScene(Scene): self.add(self.camera.theta_tracker) def stop_ambient_camera_rotation(self): + """ + This method stops all ambient camera rotation. + """ self.camera.theta_tracker.clear_updaters() self.remove(self.camera.theta_tracker) @@ -52,6 +86,31 @@ class ThreeDScene(Scene): frame_center=None, added_anims=[], **kwargs): + """ + This method animates the movement of the camera + to the given spherical coordinates. + + Parameters + ---------- + phi : (int,float) + The polar angle i.e the angle between Z_AXIS and Camera through ORIGIN in radians. + + theta : (int,float) + The azimuthal angle i.e the angle that spins the camera around the Z_AXIS. + + distance : (int, float) + The radial distance between ORIGIN and Camera. + + gamma : (int, float) + The rotation of the camera about the vector from the ORIGIN to the Camera. + + frame_center : Union[list,tuple,array] + The new center of the camera frame. + + added_anims : list + Any other animations to be played at the same time? + + """ anims = [] value_tracker_pairs = [ (phi, self.camera.phi_tracker), @@ -73,6 +132,15 @@ class ThreeDScene(Scene): self.play(*anims + added_anims) def get_moving_mobjects(self, *animations): + """ + This method returns a list of all of the Mobjects in the Scene that + are moving, that are also in the animations passed. + + Parameters + ---------- + *animations (Animation) + The animations whose mobjects will be checked. + """ moving_mobjects = Scene.get_moving_mobjects(self, *animations) camera_mobjects = self.camera.get_value_trackers() if any([cm in moving_mobjects for cm in camera_mobjects]): @@ -80,27 +148,96 @@ class ThreeDScene(Scene): return moving_mobjects def add_fixed_orientation_mobjects(self, *mobjects, **kwargs): + """ + This method is used to prevent the rotation and tilting + of mobjects as the camera moves around. The mobject can + still move in the x,y,z directions, but will always be + at the angle (relative to the camera) that it was at + when it was passed through this method.) + + Parameters + ---------- + *mobjects (Mobjects) + The Mobjects whose orientation must be fixed. + + **kwargs + Some valid kwargs are + use_static_center_func (bool) + center_func (function) + """ self.add(*mobjects) self.camera.add_fixed_orientation_mobjects(*mobjects, **kwargs) def add_fixed_in_frame_mobjects(self, *mobjects): + """ + This method is used to prevent the rotation and movement + of mobjects as the camera moves around. The mobject is + essentially overlayed, and is not impacted by the camera's + movement in any way. + + Parameters + ---------- + *mobjects (Mobjects) + The Mobjects whose orientation must be fixed. + """ self.add(*mobjects) self.camera.add_fixed_in_frame_mobjects(*mobjects) def remove_fixed_orientation_mobjects(self, *mobjects): + """ + This method "unfixes" the orientation of the mobjects + passed, meaning they will no longer be at the same angle + relative to the camera. This only makes sense if the + mobject was passed through add_fixed_orientation_mobjects first. + + Parameters + ---------- + *mobjects (Mobjects) + The Mobjects whose orientation must be unfixed. + """ self.camera.remove_fixed_orientation_mobjects(*mobjects) def remove_fixed_in_frame_mobjects(self, *mobjects): + """ + This method undoes what add_fixed_in_frame_mobjects does. + It allows the mobject to be affected by the movement of + the camera. + + Parameters + ---------- + *mobjects (Mobjects) + The Mobjects whose position and orientation must be unfixed. + """ self.camera.remove_fixed_in_frame_mobjects(*mobjects) ## def set_to_default_angled_camera_orientation(self, **kwargs): + """ + This method sets the default_angled_camera_orientation to the + keyword arguments passed, and sets the camera to that orientation. + + Parameters + ---------- + **kwargs + Some recognised kwargs are phi, theta, distance, gamma, + which have the same meaning as the parameters in set_camera_orientation. + """ config = dict(self.default_camera_orientation_kwargs) config.update(kwargs) self.set_camera_orientation(**config) class SpecialThreeDScene(ThreeDScene): + """ + This is basically ThreeDScene++ . + It has some extra configuration for + axes, spheres, lower quality etc. + + Some key differences are: + The camera shades applicable 3DMobjects by default, + except if rendering in low quality. + Some default params for Spheres and Axes have been added. + """ CONFIG = { "cut_axes_at_radius": True, "camera_config": { @@ -149,6 +286,13 @@ class SpecialThreeDScene(ThreeDScene): ThreeDScene.__init__(self, **config) def get_axes(self): + """ + Returns a set of 3D Axes. + + Returns + ------- + ThreeDAxes object + """ axes = ThreeDAxes(**self.three_d_axes_config) for axis in axes: if self.cut_axes_at_radius: @@ -170,13 +314,39 @@ class SpecialThreeDScene(ThreeDScene): return axes def get_sphere(self, **kwargs): + """ + Returns a sphere with the passed **kwargs + as properties. + + Parameters + ---------- + **kwargs + Some valid kwargs are: + Any param of a Sphere or ParametricSurface. + + Returns + ------- + Sphere + The sphere object. + """ config = merge_dicts_recursively(self.sphere_config, kwargs) return Sphere(**config) def get_default_camera_position(self): + """ + Returns the default_angled_camera position. + + Returns + ------- + dict + Dictionary of phi, theta, distance, and gamma. + """ return self.default_angled_camera_position def set_camera_to_default_position(self): + """ + Sets the camera to its default position. + """ self.set_camera_orientation( **self.default_angled_camera_position ) diff --git a/manimlib/scene/vector_space_scene.py b/manimlib/scene/vector_space_scene.py index 902ad3bd..825cf758 100644 --- a/manimlib/scene/vector_space_scene.py +++ b/manimlib/scene/vector_space_scene.py @@ -47,6 +47,21 @@ class VectorScene(Scene): } def add_plane(self, animate=False, **kwargs): + """ + Adds a NumberPlane object to the background. + + Parameters + ---------- + animate : (bool=False) + Whether or not to animate the addition of the plane via ShowCreation. + **kwargs + Any valid keyword arguments accepted by NumberPlane. + + Returns + ------- + NumberPlane + The NumberPlane object. + """ plane = NumberPlane(**kwargs) if animate: self.play(ShowCreation(plane, lag_ratio=0.5)) @@ -54,6 +69,16 @@ class VectorScene(Scene): return plane def add_axes(self, animate=False, color=WHITE, **kwargs): + """ + Adds a pair of Axes to the Scene. + + Parameters + ---------- + animate (bool=False) + Whether or not to animate the addition of the axes through ShowCreation. + color (str) + The color of the axes. Defaults to WHITE. + """ axes = Axes(color=color, tick_frequency=1) if animate: self.play(ShowCreation(axes)) @@ -61,6 +86,18 @@ class VectorScene(Scene): return axes def lock_in_faded_grid(self, dimness=0.7, axes_dimness=0.5): + """ + This method freezes the NumberPlane and Axes that were already + in the background, and adds new, manipulatable ones to the foreground. + + Parameters + ---------- + dimness (Union[int,float=0.7]) + The required dimness of the NumberPlane + + axes_dimness (Union[int,float=0.5]) + The required dimness of the Axes. + """ plane = self.add_plane() axes = plane.get_axes() plane.fade(dimness) @@ -70,6 +107,21 @@ class VectorScene(Scene): self.freeze_background() def get_vector(self, numerical_vector, **kwargs): + """ + Returns an arrow on the Plane given an input numerical vector. + + Parameters + ---------- + numerical_vector : Union(np.ndarray, list, tuple) + The Vector to plot. + **kwargs + Any valid keyword argument of Arrow. + + Returns + ------- + Arrow + The Arrow representing the Vector. + """ return Arrow( self.plane.coords_to_point(0, 0), self.plane.coords_to_point(*numerical_vector[:2]), @@ -78,6 +130,34 @@ class VectorScene(Scene): ) def add_vector(self, vector, color=YELLOW, animate=True, **kwargs): + """ + Returns the Vector after adding it to the Plane. + + Parameters + ---------- + vector Union(Arrow,list,tuple,np.ndarray) + It can be a pre-made graphical vector, or the + coordinates of one. + + color (str) + The string of the hex color of the vector. + This is only taken into consideration if + 'vector' is not an Arrow. Defaults to YELLOW. + + animate (bool=True) + Whether or not to animate the addition of the vector + by using GrowArrow + + **kwargs + Any valid keyword argument of Arrow. + These are only considered if vector is not + an Arrow. + + Returns + ------- + Arrow + The arrow representing the vector. + """ if not isinstance(vector, Arrow): vector = Vector(vector, color=color, **kwargs) if animate: @@ -86,11 +166,49 @@ class VectorScene(Scene): return vector def write_vector_coordinates(self, vector, **kwargs): + """ + Returns a column matrix indicating the vector coordinates, + after writing them to the screen. + + Parameters + ---------- + vector (Arrow) + The arrow representing the vector. + + **kwargs + Any valid keyword arguments of matrix.vector_coordinate_label + + integer_labels (True) : Whether or not to round the coordinates + to integers. + n_dim (2) : The number of dimensions of the vector. + color (WHITE) : The color of the label. + + Returns + ------- + Matrix + The column matrix representing the vector. + """ coords = vector_coordinate_label(vector, **kwargs) self.play(Write(coords)) return coords def get_basis_vectors(self, i_hat_color=X_COLOR, j_hat_color=Y_COLOR): + """ + Returns a VGroup of the Basis Vectors (1,0) and (0,1) + + Parameters + ---------- + i_hat_color (str) + The hex colour to use for the basis vector in the x direction + + j_hat_color (str) + The hex colour to use for the basis vector in the y direction + + Returns + ------- + VGroup + VGroup of the Vector Mobjects representing the basis vectors. + """ return VGroup(*[ Vector( vect, @@ -104,6 +222,21 @@ class VectorScene(Scene): ]) def get_basis_vector_labels(self, **kwargs): + """ + Returns naming labels for the basis vectors. + + Parameters + ---------- + **kwargs + Any valid keyword arguments of get_vector_label: + vector, + label (str,TexMobject) + at_tip (bool=False), + direction (str="left"), + rotate (bool), + color (str), + label_scale_factor=VECTOR_LABEL_SCALE_FACTOR (int, float), + """ i_hat, j_hat = self.get_basis_vectors() return VGroup(*[ self.get_vector_label( @@ -123,6 +256,29 @@ class VectorScene(Scene): rotate=False, color=None, label_scale_factor=VECTOR_LABEL_SCALE_FACTOR): + """ + Returns naming labels for the passed vector. + + Parameters + ---------- + vector + Vector Object for which to get the label. + at_tip (bool) + Whether or not to place the label at the tip of the vector. + direction (str="left") + If the label should be on the "left" or right of the vector. + rotate (bool=False) + Whether or not to rotate it to align it with the vector. + color (str) + The color to give the label. + label_scale_factor (Union[int,float]) + How much to scale the label by. + + Returns + ------- + TexMobject + The TexMobject of the label. + """ if not isinstance(label, TexMobject): if len(label) == 1: label = "\\vec{\\textbf{%s}}" % label @@ -150,23 +306,65 @@ class VectorScene(Scene): return label def label_vector(self, vector, label, animate=True, **kwargs): + """ + Shortcut method for creating, and animating the addition of + a label for the vector. + + Parameters + ---------- + vector (Vector) + The vector for which the label must be added. + label (TexMobject,str) + The TexMobject/string of the label. + animate (bool=True) + Whether or not to animate the labelling w/ Write + **kwargs + Any valid keyword argument of get_vector_label + + Returns + ------- + TexMobject + The TexMobject of the label. + """ label = self.get_vector_label(vector, label, **kwargs) if animate: self.play(Write(label, run_time=1)) self.add(label) return label - def position_x_coordinate(self, x_coord, x_line, vector): + def position_x_coordinate(self, x_coord, x_line, vector): #TODO Write DocStrings for this. x_coord.next_to(x_line, -np.sign(vector[1]) * UP) x_coord.set_color(X_COLOR) return x_coord - def position_y_coordinate(self, y_coord, y_line, vector): + def position_y_coordinate(self, y_coord, y_line, vector): #TODO Write DocStrings for this. y_coord.next_to(y_line, np.sign(vector[0]) * RIGHT) y_coord.set_color(Y_COLOR) return y_coord def coords_to_vector(self, vector, coords_start=2 * RIGHT + 2 * UP, clean_up=True): + """ + This method writes the vector as a column matrix (henceforth called the label), + takes the values in it one by one, and form the corresponding + lines that make up the x and y components of the vector. Then, an + Vector() based vector is created between the lines on the Screen. + + Parameters + ---------- + vector Union(np.ndarray, list, tuple) + The vector to show. + + coords_start Union(np.ndarray,list,tuple) + The starting point of the location of + the label of the vector that shows it + numerically. + Defaults to 2 * RIGHT + 2 * UP or (2,2) + + clean_up (bool=True) + Whether or not to remove whatever + this method did after it's done. + + """ starting_mobjects = list(self.mobjects) array = Matrix(vector) array.shift(coords_start) @@ -200,6 +398,26 @@ class VectorScene(Scene): self.add(*starting_mobjects) def vector_to_coords(self, vector, integer_labels=True, clean_up=True): + """ + This method displays vector as a Vector() based vector, and then shows + the corresponding lines that make up the x and y components of the vector. + Then, a column matrix (henceforth called the label) is created near the + head of the Vector. + + Parameters + ---------- + vector Union(np.ndarray, list, tuple) + The vector to show. + + integer_label (bool=True) + Whether or not to round the value displayed. + in the vector's label to the nearest integer + + clean_up (bool=True) + Whether or not to remove whatever + this method did after it's done. + + """ starting_mobjects = list(self.mobjects) show_creation = False if isinstance(vector, Arrow): @@ -250,6 +468,17 @@ class VectorScene(Scene): return array, x_line, y_line def show_ghost_movement(self, vector): + """ + This method plays an animation that partially shows the entire plane moving + in the direction of a particular vector. This is useful when you wish to + convey the idea of mentally moving the entire plane in a direction, without + actually moving the plane. + + Parameters + ---------- + vector (Union[Arrow, list, tuple, np.ndarray]) + The vector which indicates the direction of movement. + """ if isinstance(vector, Arrow): vector = vector.get_end() - vector.get_start() elif len(vector) == 2: @@ -275,6 +504,10 @@ class VectorScene(Scene): class LinearTransformationScene(VectorScene): + """ + This scene contains special methods that make it + especially suitable for showing Linear Transformations. + """ CONFIG = { "include_background_plane": True, "include_foreground_plane": True, @@ -341,26 +574,85 @@ class LinearTransformationScene(VectorScene): self.add(self.basis_vectors) def add_special_mobjects(self, mob_list, *mobs_to_add): + """ + Adds mobjects to a separate list that can be tracked, + if these mobjects have some extra importance. + + Parameters + ---------- + mob_list (list) + The special list to which you want to add + these mobjects. + + *mobs_to_add (Mobject) + The mobjects to add. + + """ for mobject in mobs_to_add: if mobject not in mob_list: mob_list.append(mobject) self.add(mobject) def add_background_mobject(self, *mobjects): + """ + Adds the mobjects to the special list + self.background_mobjects. + + Parameters + ---------- + *mobjects (Mobject) + The mobjects to add to the list. + """ self.add_special_mobjects(self.background_mobjects, *mobjects) # TODO, this conflicts with Scene.add_fore def add_foreground_mobject(self, *mobjects): + """ + Adds the mobjects to the special list + self.foreground_mobjects. + + Parameters + ---------- + *mobjects (Mobject) + The mobjects to add to the list + """ self.add_special_mobjects(self.foreground_mobjects, *mobjects) def add_transformable_mobject(self, *mobjects): + """ + Adds the mobjects to the special list + self.transformable_mobjects. + + Parameters + ---------- + *mobjects (Mobject) + The mobjects to add to the list. + """ self.add_special_mobjects(self.transformable_mobjects, *mobjects) def add_moving_mobject(self, mobject, target_mobject=None): + """ + Adds the mobject to the special list + self.moving_mobject, and adds a property + to the mobject called mobject.target, which + keeps track of what the mobject will move to + or become etc. + + Parameters + ---------- + mobject (Mobject) + The mobjects to add to the list + + target_mobject (Mobject) + What the moving_mobject goes to, etc. + """ mobject.target = target_mobject self.add_special_mobjects(self.moving_mobjects, mobject) def get_unit_square(self, color=YELLOW, opacity=0.3, stroke_width=3): + """ + Returns a unit square for the current NumberPlane. + """ square = self.square = Rectangle( color=color, width=self.plane.get_x_unit_size(), @@ -374,6 +666,24 @@ class LinearTransformationScene(VectorScene): return square def add_unit_square(self, animate=False, **kwargs): + """ + Adds a unit square to the scene via + self.get_unit_square. + + Parameters + ---------- + animate (bool) + Whether or not to animate the addition + with DrawBorderThenFill. + **kwargs + Any valid keyword arguments of + self.get_unit_square() + + Returns + ------- + Square + The unit square. + """ square = self.get_unit_square(**kwargs) if animate: self.play( @@ -386,6 +696,29 @@ class LinearTransformationScene(VectorScene): return self def add_vector(self, vector, color=YELLOW, **kwargs): + """ + Adds a vector to the scene, and puts it in the special + list self.moving_vectors. + + Parameters + ---------- + vector Union(Arrow,list,tuple,np.ndarray) + It can be a pre-made graphical vector, or the + coordinates of one. + + color (str) + The string of the hex color of the vector. + This is only taken into consideration if + 'vector' is not an Arrow. Defaults to YELLOW. + + **kwargs + Any valid keyword argument of VectorScene.add_vector. + + Returns + ------- + Arrow + The arrow representing the vector. + """ vector = VectorScene.add_vector( self, vector, color=color, **kwargs ) @@ -393,6 +726,24 @@ class LinearTransformationScene(VectorScene): return vector def write_vector_coordinates(self, vector, **kwargs): + """ + Returns a column matrix indicating the vector coordinates, + after writing them to the screen, and adding them to the + special list self.foreground_mobjects + + Parameters + ---------- + vector (Arrow) + The arrow representing the vector. + + **kwargs + Any valid keyword arguments of VectorScene.write_vector_coordinates + + Returns + ------- + Matrix + The column matrix representing the vector. + """ coords = VectorScene.write_vector_coordinates(self, vector, **kwargs) self.add_foreground_mobject(coords) return coords @@ -402,6 +753,26 @@ class LinearTransformationScene(VectorScene): transformation_name="L", new_label=None, **kwargs): + """ + Method for creating, and animating the addition of + a transformable label for the vector. + + Parameters + ---------- + vector (Vector) + The vector for which the label must be added. + label (TexMobject,str) + The TexMobject/string of the label. + new_label (TexMobject,str,None) + What the label should display after a Linear Transformation + **kwargs + Any valid keyword argument of get_vector_label + + Returns + ------- + TexMobject + The TexMobject of the label. + """ label_mob = self.label_vector(vector, label, **kwargs) if new_label: label_mob.target_text = new_label @@ -418,6 +789,27 @@ class LinearTransformationScene(VectorScene): return label_mob def add_title(self, title, scale_factor=1.5, animate=False): + """ + Adds a title, after scaling it, adding a background rectangle, + moving it to the top and adding it to foreground_mobjects adding + it as a local variable of self. Returns the Scene. + + Parameters + ---------- + title (str,TexMobject,TextMobject) + What the title should be. + + scale_factor (int,float=1.5) + How much the title should be scaled by. + + animate (bool=False) + Whether or not to animate the addition. + + Returns + ------- + LinearTransformationScene + The scene with the title added to it. + """ if not isinstance(title, Mobject): title = TextMobject(title).scale(scale_factor) title.to_edge(UP) @@ -429,9 +821,28 @@ class LinearTransformationScene(VectorScene): return self def get_matrix_transformation(self, matrix): + """ + Returns a function corresponding to the linear + transformation represented by the matrix passed. + + Parameters + ---------- + matrix (np.ndarray, list, tuple) + The matrix. + """ return self.get_transposed_matrix_transformation(np.array(matrix).T) def get_transposed_matrix_transformation(self, transposed_matrix): + """ + Returns a function corresponding to the linear + transformation represented by the transposed + matrix passed. + + Parameters + ---------- + matrix (np.ndarray, list, tuple) + The matrix. + """ transposed_matrix = np.array(transposed_matrix) if transposed_matrix.shape == (2, 2): new_matrix = np.identity(3) @@ -442,6 +853,22 @@ class LinearTransformationScene(VectorScene): return lambda point: np.dot(point, transposed_matrix) def get_piece_movement(self, pieces): + """ + This method returns an animation that moves an arbitrary + mobject in "pieces" to its corresponding .target value. + If self.leave_ghost_vectors is True, ghosts of the original + positions/mobjects are left on screen + + Parameters + ---------- + pieces (Union[list, tuple, np.array]) + The pieces for which the movement must be shown. + + Returns + ------- + Animation + The animation of the movement. + """ start = VGroup(*pieces) target = VGroup(*[mob.target for mob in pieces]) if self.leave_ghost_vectors: @@ -449,6 +876,23 @@ class LinearTransformationScene(VectorScene): return Transform(start, target, lag_ratio=0) def get_moving_mobject_movement(self, func): + """ + This method returns an animation that moves a mobject + in "self.moving_mobjects" to its corresponding .target value. + func is a function that determines where the .target goes. + + Parameters + ---------- + + func (function) + The function that determines where the .target of + the moving mobject goes. + + Returns + ------- + Animation + The animation of the movement. + """ for m in self.moving_mobjects: if m.target is None: m.target = m.copy() @@ -457,6 +901,23 @@ class LinearTransformationScene(VectorScene): return self.get_piece_movement(self.moving_mobjects) def get_vector_movement(self, func): + """ + This method returns an animation that moves a mobject + in "self.moving_vectors" to its corresponding .target value. + func is a function that determines where the .target goes. + + Parameters + ---------- + + func (function) + The function that determines where the .target of + the moving mobject goes. + + Returns + ------- + Animation + The animation of the movement. + """ for v in self.moving_vectors: v.target = Vector(func(v.get_end()), color=v.get_color()) norm = get_norm(v.target.get_end()) @@ -465,6 +926,15 @@ class LinearTransformationScene(VectorScene): return self.get_piece_movement(self.moving_vectors) def get_transformable_label_movement(self): + """ + This method returns an animation that moves all labels + in "self.transformable_labels" to its corresponding .target . + + Returns + ------- + Animation + The animation of the movement. + """ for l in self.transformable_labels: l.target = self.get_vector_label( l.vector.target, l.target_text, **l.kwargs @@ -472,12 +942,48 @@ class LinearTransformationScene(VectorScene): return self.get_piece_movement(self.transformable_labels) def apply_matrix(self, matrix, **kwargs): + """ + Applies the transformation represented by the + given matrix to the number plane, and each vector/similar + mobject on it. + + Parameters + ---------- + matrix (Union[np.ndarray, list, tuple]) + The matrix. + **kwargs + Any valid keyword argument of self.apply_transposed_matrix() + """ self.apply_transposed_matrix(np.array(matrix).T, **kwargs) def apply_inverse(self, matrix, **kwargs): + """ + This method applies the linear transformation + represented by the inverse of the passed matrix + to the number plane, and each vector/similar mobject on it. + + Parameters + ---------- + matrix (Union[np.ndarray, list, tuple]) + The matrix whose inverse is to be applied. + **kwargs + Any valid keyword argument of self.apply_matrix() + """ self.apply_matrix(np.linalg.inv(matrix), **kwargs) def apply_transposed_matrix(self, transposed_matrix, **kwargs): + """ + Applies the transformation represented by the + given transposed matrix to the number plane, + and each vector/similar mobject on it. + + Parameters + ---------- + matrix (Union[np.ndarray, list, tuple]) + The matrix. + **kwargs + Any valid keyword argument of self.apply_function() + """ func = self.get_transposed_matrix_transformation(transposed_matrix) if "path_arc" not in kwargs: net_rotation = np.mean([ @@ -488,14 +994,56 @@ class LinearTransformationScene(VectorScene): self.apply_function(func, **kwargs) def apply_inverse_transpose(self, t_matrix, **kwargs): + """ + Applies the inverse of the transformation represented + by the given transposed matrix to the number plane and each + vector/similar mobject on it. + + Parameters + ---------- + matrix (Union[np.ndarray, list, tuple]) + The matrix. + **kwargs + Any valid keyword argument of self.apply_transposed_matrix() + """ t_inv = np.linalg.inv(np.array(t_matrix).T).T self.apply_transposed_matrix(t_inv, **kwargs) def apply_nonlinear_transformation(self, function, **kwargs): + """ + Applies the non-linear transformation represented + by the given function to the number plane and each + vector/similar mobject on it. + + Parameters + ---------- + function (Function) + The function. + **kwargs + Any valid keyword argument of self.apply_function() + """ self.plane.prepare_for_nonlinear_transform() self.apply_function(function, **kwargs) def apply_function(self, function, added_anims=[], **kwargs): + """ + Applies the given function to each of the mobjects in + self.transformable_mobjects, and plays the animation showing + this. + + Parameters + ---------- + function (Function) + The function that affects each point + of each mobject in self.transformable_mobjects. + + added_anims (list) + Any other animations that need to be played + simulataneously with this. + + **kwargs + Any valid keyword argument of a self.play() call. + """ if "run_time" not in kwargs: kwargs["run_time"] = 3 anims = [ diff --git a/manimlib/scene/zoomed_scene.py b/manimlib/scene/zoomed_scene.py index b0065dd5..44ac52ba 100644 --- a/manimlib/scene/zoomed_scene.py +++ b/manimlib/scene/zoomed_scene.py @@ -11,6 +11,11 @@ from manimlib.utils.simple_functions import fdiv class ZoomedScene(MovingCameraScene): + """ + This is a Scene with special configurations made for when + a particular part of the scene must be zoomed in on and displayed + separately. + """ CONFIG = { "camera_class": MultiCamera, "zoomed_display_height": 3, @@ -30,6 +35,10 @@ class ZoomedScene(MovingCameraScene): } def setup(self): + """ + This method is used internally by Manim to + setup the scene for proper use. + """ MovingCameraScene.setup(self) # Initialize camera and display zoomed_camera = MovingCamera(**self.zoomed_camera_config) @@ -56,6 +65,16 @@ class ZoomedScene(MovingCameraScene): self.zoomed_display = zoomed_display def activate_zooming(self, animate=False): + """ + This method is used to activate the zooming for + the zoomed_camera. + + Parameters + ---------- + animate (bool=False) + Whether or not to animate the activation + of the zoomed camera. + """ self.zoom_activated = True self.camera.add_image_mobject_from_camera(self.zoomed_display) if animate: @@ -67,6 +86,21 @@ class ZoomedScene(MovingCameraScene): ) def get_zoom_in_animation(self, run_time=2, **kwargs): + """ + Returns the animation of camera zooming in. + + Parameters + ---------- + run_time (Union[int,float=2]) + The run_time of the animation of the camera zooming in. + **kwargs + Any valid keyword arguments of ApplyMethod() + + Returns + ------- + ApplyMethod + The animation of the camera zooming in. + """ frame = self.zoomed_camera.frame full_frame_height = self.camera.get_frame_height() full_frame_width = self.camera.get_frame_width() @@ -78,12 +112,32 @@ class ZoomedScene(MovingCameraScene): return ApplyMethod(frame.restore, run_time=run_time, **kwargs) def get_zoomed_display_pop_out_animation(self, **kwargs): + """ + This is the animation of the popping out of the + mini-display that shows the content of the zoomed + camera. + + Returns + ------- + ApplyMethod + The Animation of the Zoomed Display popping out. + """ display = self.zoomed_display display.save_state(use_deepcopy=True) display.replace(self.zoomed_camera.frame, stretch=True) return ApplyMethod(display.restore) def get_zoom_factor(self): + """ + Returns the Zoom factor of the Zoomed camera. + Defined as the ratio between the height of the + zoomed camera and the height of the zoomed mini + display. + Returns + ------- + float + The zoom factor. + """ return fdiv( self.zoomed_camera.frame.get_height(), self.zoomed_display.get_height() diff --git a/sanim b/sanim new file mode 160000 index 00000000..19aa1e4b --- /dev/null +++ b/sanim @@ -0,0 +1 @@ +Subproject commit 19aa1e4b2e1c8acff58e075d3d8bc4bd8f112751