Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 154 additions & 29 deletions manim/mobject/geometry/arc.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def construct(self):

import manim.mobject.geometry.tips as tips
from manim.mobject.geometry.line import Line
from manim.mobject.geometry.tips import ArrowTip
from manim.mobject.mobject import Mobject
from manim.mobject.text.tex_mobject import SingleStringMathTex, Tex
from manim.mobject.text.text_mobject import Text
Expand Down Expand Up @@ -114,7 +115,7 @@ def __init__(
def add_tip(
self,
tip: tips.ArrowTip | None = None,
tip_shape: type[tips.ArrowTip] | None = None,
tip_shape: type[ArrowTip] | None = None,
tip_length: float | None = None,
tip_width: float | None = None,
at_start: bool = False,
Expand Down Expand Up @@ -483,10 +484,13 @@ def __init__(
)
arc_height = radius - np.sqrt(radius**2 - halfdist**2)
angle = np.arccos((radius - arc_height) / radius) * sign

super().__init__(radius=radius, angle=angle, **kwargs)
if angle == 0:
self.set_points_as_corners(np.array([LEFT, RIGHT]))

start = self.make3Dpoint(start)
end = self.make3Dpoint(end)

self.put_start_and_end_on(start, end)

if radius is None:
Expand All @@ -499,6 +503,21 @@ def __init__(
else:
self.radius = np.inf

def make3Dpoint(
self,
point: Point3DLike,
) -> Point3D:
"""Transforms a point, i.e. a 1D numpy array of shape (2,) into 1D numpy array of shape (3,).

Parameters
----------
point
The point which is to be converted to a 3D point.
"""
if len(point) == 2:
return np.hstack([point, 0])
return np.array(point)


class TangentialArc(ArcBetweenPoints):
"""
Expand Down Expand Up @@ -556,9 +575,6 @@ def __init__(
corner: Any = (1, 1),
**kwargs: Any,
):
self.line1 = line1
self.line2 = line2

intersection_point = line_intersection(
[line1.get_start(), line1.get_end()], [line2.get_start(), line2.get_end()]
)
Expand Down Expand Up @@ -594,35 +610,53 @@ def __init__(

class CurvedArrow(ArcBetweenPoints):
def __init__(
self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
self,
start_point: Point3DLike,
end_point: Point3DLike,
tip_shape: type[ArrowTip] | None = None,
**kwargs: Any,
) -> None:
from manim.mobject.geometry.tips import ArrowTriangleFilledTip

tip_shape = kwargs.pop("tip_shape", ArrowTriangleFilledTip)
if tip_shape is None:
tip_shape = ArrowTriangleFilledTip
super().__init__(start_point, end_point, **kwargs)
self.add_tip(tip_shape=tip_shape)


class CurvedDoubleArrow(CurvedArrow):
def __init__(
self, start_point: Point3DLike, end_point: Point3DLike, **kwargs: Any
self,
start_point: Point3DLike,
end_point: Point3DLike,
tip_shape_at_start: type[ArrowTip] | None = None,
tip_shape_at_end: type[ArrowTip] | None = None,
**kwargs: Any,
) -> None:
if "tip_shape_end" in kwargs:
kwargs["tip_shape"] = kwargs.pop("tip_shape_end")
from manim.mobject.geometry.tips import ArrowTriangleFilledTip

tip_shape_start = kwargs.pop("tip_shape_start", ArrowTriangleFilledTip)
super().__init__(start_point, end_point, **kwargs)
self.add_tip(at_start=True, tip_shape=tip_shape_start)
if tip_shape_at_start is None:
tip_shape_at_start = ArrowTriangleFilledTip
if tip_shape_at_end is None:
tip_shape_at_end = ArrowTriangleFilledTip

super().__init__(start_point, end_point, tip_shape=tip_shape_at_end, **kwargs)
self.add_tip(at_start=True, tip_shape=tip_shape_at_start)


class Circle(Arc):
"""A circle.

Parameters
----------
center
Center of the Circle.
If it's not provided by user, then it defaults to ORIGIN,
as the arc_center in Arc class is set to default value of ORIGIN.
radius
Radius of the Circle
color
The color of the shape.
The stroke color of the circle.
kwargs
Additional arguments to be passed to :class:`Arc`

Expand All @@ -645,8 +679,11 @@ def __init__(
self,
radius: float | None = None,
color: ParsableManimColor = RED,
center: Point3DLike | None = None,
**kwargs: Any,
) -> None:
if center is not None:
kwargs["arc_center"] = center
super().__init__(
radius=radius,
start_angle=0,
Expand Down Expand Up @@ -697,14 +734,24 @@ def construct(self):
group = Group(group1, group2, group3).arrange(buff=1)
self.add(group)
"""
# Ignores dim_to_match and stretch; result will always be a circle
# TODO: Perhaps create an ellipse class to handle single-dimension stretching
if dim_to_match != 0:
warnings.warn(
"The 'dim_to_match' is ignored "
"and it will be deprecated in a future version of Manim Community.",
DeprecationWarning,
stacklevel=2,
)

# Something goes wrong here when surrounding lines?
# TODO: Figure out and fix
self.replace(mobject, dim_to_match, stretch)
if stretch:
warnings.warn(
"The 'stretch' parameter is ignored "
"and it will be removed in a future version of Manim Community.",
DeprecationWarning,
stacklevel=2,
)

self.width = np.sqrt(mobject.width**2 + mobject.height**2)
self.move_to(mobject.get_center())
return self.scale(buffer_factor)

def point_at_angle(self, angle: float) -> Point3D:
Expand Down Expand Up @@ -768,7 +815,7 @@ def construct(self):
)
# np.linalg.norm returns floating[Any] which is not compatible with float
radius = cast(float, np.linalg.norm(p1 - center))
return Circle(radius=radius, **kwargs).shift(center)
return Circle(center=center, radius=radius, **kwargs)


class Dot(Circle):
Expand Down Expand Up @@ -812,7 +859,7 @@ def __init__(
**kwargs: Any,
) -> None:
super().__init__(
arc_center=point,
center=point,
radius=radius,
stroke_width=stroke_width,
fill_opacity=fill_opacity,
Expand All @@ -822,17 +869,34 @@ def __init__(


class AnnotationDot(Dot):
"""A dot with bigger radius and bold stroke to annotate scenes."""
"""A dot with bigger radius and bold stroke to annotate scenes.
Parameters
----------
point
Location of the AnnotationDot.
radius
Radius of the AnnotationDot.
stroke_width
Stroke width of the AnnotationDot's circumference.
stroke_color
Stroke color of the AnnotationDot's circumference.
fill_color
Fill color of the AnnotationDot.

"""

def __init__(
self,
radius: float = DEFAULT_DOT_RADIUS * 1.3,
stroke_width: float = 5,
stroke_color: ParsableManimColor = WHITE,
fill_color: ParsableManimColor = BLUE,
*,
point: Point3DLike = ORIGIN,
**kwargs: Any,
) -> None:
super().__init__(
point=point,
radius=radius,
stroke_width=stroke_width,
stroke_color=stroke_color,
Expand All @@ -846,6 +910,8 @@ class LabeledDot(Dot):

Parameters
----------
point
Location of the LabeledDot.
label
The label of the :class:`Dot`. This is rendered as :class:`~.MathTex`
by default (i.e., when passing a :class:`str`), but other classes
Expand Down Expand Up @@ -881,6 +947,8 @@ def __init__(
label: str | SingleStringMathTex | Text | Tex,
radius: float | None = None,
buff: float = SMALL_BUFF,
*,
point: Point3DLike = ORIGIN,
**kwargs: Any,
) -> None:
if isinstance(label, str):
Expand All @@ -894,7 +962,7 @@ def __init__(
radius = buff + float(
np.linalg.norm([rendered_label.width, rendered_label.height]) / 2
)
super().__init__(radius=radius, **kwargs)
super().__init__(point=point, radius=radius, **kwargs)
rendered_label.move_to(self.get_center())
self.add(rendered_label)

Expand All @@ -904,6 +972,8 @@ class Ellipse(Circle):

Parameters
----------
center
The ellipse is centered at this point.
width
The horizontal width of the ellipse.
height
Expand All @@ -924,8 +994,17 @@ def construct(self):
self.add(ellipse_group)
"""

def __init__(self, width: float = 2, height: float = 1, **kwargs: Any) -> None:
super().__init__(**kwargs)
def __init__(
self,
width: float = 2,
height: float = 1,
*,
center: Point3DLike = ORIGIN,
**kwargs: Any,
) -> None:
if "arc_center" in kwargs:
center = kwargs.pop("arc_center")
super().__init__(center=center, **kwargs)
self.stretch_to_fit_width(width)
self.stretch_to_fit_height(height)

Expand Down Expand Up @@ -985,11 +1064,16 @@ def __init__(
fill_opacity: float = 1,
stroke_width: float = 0,
color: ParsableManimColor = WHITE,
*,
arc_center: Point3DLike | None = None,
**kwargs: Any,
) -> None:
if arc_center is None:
arc_center = ORIGIN
self.inner_radius = inner_radius
self.outer_radius = outer_radius
super().__init__(
arc_center=arc_center,
start_angle=start_angle,
angle=angle,
fill_opacity=fill_opacity,
Expand All @@ -1009,12 +1093,15 @@ def generate_points(self) -> None:
for radius in (self.inner_radius, self.outer_radius)
)
outer_arc.reverse_points()
# Cairo and OpenGL renderer both use non-zero winding rule.
# That's why the points of outer_arc are reversed.
# If instead of outer_arc, the points of inner_arc is reversed, the output is exactly the same.
self.append_points(inner_arc.points)
self.add_line_to(outer_arc.points[0])
self.append_points(outer_arc.points)
self.add_line_to(inner_arc.points[0])

def init_points(self) -> None:
def init_points(self) -> None: # used by OpenGLRenderer.
self.generate_points()


Expand All @@ -1035,8 +1122,26 @@ def construct(self):
self.add(sector, sector2)
"""

def __init__(self, radius: float = 1, **kwargs: Any) -> None:
super().__init__(inner_radius=0, outer_radius=radius, **kwargs)
def __init__(
self,
radius: float = 1,
*,
arc_center: Point3DLike | None = None,
start_angle: float = 0,
angle: float = TAU / 4,
**kwargs: Any,
) -> None:
if arc_center is None:
arc_center = ORIGIN

super().__init__(
arc_center=arc_center,
start_angle=start_angle,
angle=angle,
inner_radius=0,
outer_radius=radius,
**kwargs,
)


class Annulus(Circle):
Expand Down Expand Up @@ -1071,20 +1176,40 @@ def __init__(
stroke_width: float = 0,
color: ParsableManimColor = WHITE,
mark_paths_closed: bool = False,
*,
center: Point3DLike | None = None,
**kwargs: Any,
) -> None:
if mark_paths_closed is not False:
warnings.warn(
"mark_path_closed is an unused parameter ."
"It will be removed in future version of Manim Community.",
DeprecationWarning,
stacklevel=2,
)
if "arc_center" in kwargs:
center = kwargs.pop("arc_center")
if center is None:
center = ORIGIN
self.mark_paths_closed = mark_paths_closed # is this even used?
self.inner_radius = inner_radius
self.outer_radius = outer_radius
super().__init__(
fill_opacity=fill_opacity, stroke_width=stroke_width, color=color, **kwargs
center=center,
fill_opacity=fill_opacity,
stroke_width=stroke_width,
color=color,
**kwargs,
)

def generate_points(self) -> None:
self.radius = self.outer_radius
outer_circle = Circle(radius=self.outer_radius)
inner_circle = Circle(radius=self.inner_radius)
inner_circle.reverse_points()
# Cairo and OpenGL renderer both use non-zero winding rule.
# That's why the points of inner_circle are reversed.
# If instead of inner_circle, the points of outer_circle are reversed, the output is exactly the same.
self.append_points(outer_circle.points)
self.append_points(inner_circle.points)
self.shift(self.arc_center)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_graphical_units/test_geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,8 @@ def test_CurvedArrowCustomTip(scene):
double_arrow = CurvedDoubleArrow(
LEFT,
RIGHT,
tip_shape_start=ArrowCircleTip,
tip_shape_end=ArrowSquareFilledTip,
tip_shape_at_start=ArrowCircleTip,
tip_shape_at_end=ArrowSquareFilledTip,
)
scene.add(arrow, double_arrow)

Expand Down
Loading