diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 00b4299a52..6584daadd8 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -299,7 +299,7 @@ Animations path.become(previous_path) path.add_updater(update_path) self.add(path, dot) - self.play(Rotating(dot, radians=PI, about_point=RIGHT, run_time=2)) + self.play(Rotating(dot, angle=PI, about_point=RIGHT, run_time=2)) self.wait() self.play(dot.animate.shift(UP)) self.play(dot.animate.shift(LEFT)) diff --git a/manim/animation/rotation.py b/manim/animation/rotation.py index 7bdd42238a..983f239161 100644 --- a/manim/animation/rotation.py +++ b/manim/animation/rotation.py @@ -19,19 +19,85 @@ class Rotating(Animation): + """Animation that rotates a Mobject. + + Parameters + ---------- + mobject + The mobject to be rotated. + angle + The rotation angle in radians. Predefined constants such as ``DEGREES`` + can also be used to specify the angle in degrees. + axis + The rotation axis as a numpy vector. + about_point + The rotation center. + about_edge + If ``about_point`` is ``None``, this argument specifies + the direction of the bounding box point to be taken as + the rotation center. + run_time + The duration of the animation in seconds. + rate_func + The function defining the animation progress based on the relative + runtime (see :mod:`~.rate_functions`) . + **kwargs + Additional keyword arguments passed to :class:`~.Animation`. + + Examples + -------- + .. manim:: RotatingDemo + + class RotatingDemo(Scene): + def construct(self): + circle = Circle(radius=1, color=BLUE) + line = Line(start=ORIGIN, end=RIGHT) + arrow = Arrow(start=ORIGIN, end=RIGHT, buff=0, color=GOLD) + vg = VGroup(circle,line,arrow) + self.add(vg) + anim_kw = {"about_point": arrow.get_start(), "run_time": 1} + self.play(Rotating(arrow, 180*DEGREES, **anim_kw)) + self.play(Rotating(arrow, PI, **anim_kw)) + self.play(Rotating(vg, PI, about_point=RIGHT)) + self.play(Rotating(vg, PI, axis=UP, about_point=ORIGIN)) + self.play(Rotating(vg, PI, axis=RIGHT, about_edge=UP)) + self.play(vg.animate.move_to(ORIGIN)) + + .. manim:: RotatingDifferentAxis + + class RotatingDifferentAxis(ThreeDScene): + def construct(self): + axes = ThreeDAxes() + cube = Cube() + arrow2d = Arrow(start=[0, -1.2, 1], end=[0, 1.2, 1], color=YELLOW_E) + cube_group = VGroup(cube,arrow2d) + self.set_camera_orientation(gamma=0, phi=40*DEGREES, theta=40*DEGREES) + self.add(axes, cube_group) + play_kw = {"run_time": 1.5} + self.play(Rotating(cube_group, PI), **play_kw) + self.play(Rotating(cube_group, PI, axis=UP), **play_kw) + self.play(Rotating(cube_group, 180*DEGREES, axis=RIGHT), **play_kw) + self.wait(0.5) + + See also + -------- + :class:`~.Rotate`, :meth:`~.Mobject.rotate` + + """ + def __init__( self, mobject: Mobject, + angle: np.ndarray = TAU, axis: np.ndarray = OUT, - radians: np.ndarray = TAU, about_point: np.ndarray | None = None, about_edge: np.ndarray | None = None, - run_time: float = 5, + run_time: float = 2, rate_func: Callable[[float], float] = linear, **kwargs, ) -> None: + self.angle = angle self.axis = axis - self.radians = radians self.about_point = about_point self.about_edge = about_edge super().__init__(mobject, run_time=run_time, rate_func=rate_func, **kwargs) @@ -39,7 +105,7 @@ def __init__( def interpolate_mobject(self, alpha: float) -> None: self.mobject.become(self.starting_mobject) self.mobject.rotate( - self.rate_func(alpha) * self.radians, + self.rate_func(alpha) * self.angle, axis=self.axis, about_point=self.about_point, about_edge=self.about_edge, @@ -80,6 +146,10 @@ def construct(self): Rotate(Square(side_length=0.5), angle=2*PI, rate_func=linear), ) + See also + -------- + :class:`~.Rotating`, :meth:`~.Mobject.rotate` + """ def __init__( diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 6ad3ece777..f7e01f2696 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -387,9 +387,9 @@ def construct(self): will interpolate the :class:`~.Mobject` between its points prior to ``.animate`` and its points after applying ``.animate`` to it. This may result in unexpected behavior when attempting to interpolate along paths, - or rotations. + or rotations (see :meth:`.rotate`). If you want animations to consider the points between, consider using - :class:`~.ValueTracker` with updaters instead. + :class:`~.ValueTracker` with updaters instead (see :meth:`.add_updater`). """ return _AnimationBuilder(self) @@ -1029,6 +1029,9 @@ def construct(self): :meth:`get_updaters` :meth:`remove_updater` :class:`~.UpdateFromFunc` + :class:`~.Rotating` + :meth:`rotate` + :attr:`~.Mobject.animate` """ if index is None: self.updaters.append(update_function) @@ -1282,7 +1285,64 @@ def rotate( about_point: Point3DLike | None = None, **kwargs, ) -> Self: - """Rotates the :class:`~.Mobject` about a certain point.""" + """Rotates the :class:`~.Mobject` around a specified axis and point. + + Parameters + ---------- + angle + The angle of rotation in radians. Predefined constants such as ``DEGREES`` + can also be used to specify the angle in degrees. + axis + The rotation axis (see :class:`~.Rotating` for more). + about_point + The point about which the mobject rotates. If ``None``, rotation occurs around + the center of the mobject. + **kwargs + Additional keyword arguments passed to :meth:`apply_points_function_about_point`, + such as ``about_edge``. + + Returns + ------- + :class:`Mobject` + ``self`` (for method chaining) + + + .. note:: + To animate a rotation, use :class:`~.Rotating` or :class:`~.Rotate` + instead of ``.animate.rotate(...)``. + The ``.animate.rotate(...)`` syntax only applies a transformation + from the initial state to the final rotated state + (interpolation between the two states), without showing proper rotational motion + based on the angle (from 0 to the given angle). + + Examples + -------- + + .. manim:: RotateMethodExample + :save_last_frame: + + class RotateMethodExample(Scene): + def construct(self): + circle = Circle(radius=1, color=BLUE) + line = Line(start=ORIGIN, end=RIGHT) + arrow1 = Arrow(start=ORIGIN, end=RIGHT, buff=0, color=GOLD) + group1 = VGroup(circle, line, arrow1) + + group2 = group1.copy() + arrow2 = group2[2] + arrow2.rotate(angle=PI / 4, about_point=arrow2.get_start()) + + group3 = group1.copy() + arrow3 = group3[2] + arrow3.rotate(angle=120 * DEGREES, about_point=arrow3.get_start()) + + self.add(VGroup(group1, group2, group3).arrange(RIGHT, buff=1)) + + See also + -------- + :class:`~.Rotating`, :class:`~.Rotate`, :attr:`~.Mobject.animate`, :meth:`apply_points_function_about_point` + + """ rot_matrix = rotation_matrix(angle, axis) self.apply_points_function_about_point( lambda points: np.dot(points, rot_matrix.T), about_point, **kwargs @@ -3143,7 +3203,7 @@ def override_animate(method) -> types.FunctionType: .. seealso:: - :attr:`Mobject.animate` + :attr:`~.Mobject.animate` .. note::