mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
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 <aathishs@Aathishs-MacBook-Air.local>
This commit is contained in:
parent
cf656e9c21
commit
a529a59abf
9 changed files with 2111 additions and 16 deletions
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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 <height_in_pixels_of_video>p<frame_rate>
|
||||
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
|
||||
|--<name_of_file_containing_scene>
|
||||
|--<height_in_pixels_of_video>p<frame_rate>
|
||||
|--<scene_name>.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))
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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()
|
||||
|
|
1
sanim
Submodule
1
sanim
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 19aa1e4b2e1c8acff58e075d3d8bc4bd8f112751
|
Loading…
Add table
Reference in a new issue