diff --git a/manim/mobject/geometry.py b/manim/mobject/geometry.py index 3ed68fba98..910f78024f 100644 --- a/manim/mobject/geometry.py +++ b/manim/mobject/geometry.py @@ -232,18 +232,28 @@ def get_length(self): class Arc(TipableVMobject): """A circular arc.""" - CONFIG = { - "radius": 1.0, - "num_components": 9, - "anchors_span_full_range": True, - "arc_center": ORIGIN, - } - - def __init__(self, start_angle=0, angle=TAU / 4, **kwargs): + def __init__( + self, + num_components=9, + arc_center=ORIGIN, + start_angle=0, + angle=TAU / 4, + radius=1.0, + **kwargs + ): + self.num_components = num_components + self.arc_center = arc_center + self.radius = radius self.start_angle = start_angle self.angle = angle self._failed_to_get_center = False - VMobject.__init__(self, **kwargs) + VMobject.__init__( + self, + num_components=num_components, + arc_center=arc_center, + radius=radius, + **kwargs + ) def generate_points(self): self.set_pre_positioned_points() @@ -307,9 +317,7 @@ def stop_angle(self): class ArcBetweenPoints(Arc): - """ - Inherits from Arc and additionally takes 2 points between which the arc is spanned. - """ + """Circle arc spanning two points.""" def __init__(self, start, end, angle=TAU / 4, radius=None, **kwargs): if radius is not None: @@ -327,8 +335,10 @@ def __init__(self, start, end, angle=TAU / 4, radius=None, **kwargs): ) arc_height = radius - math.sqrt(radius ** 2 - halfdist ** 2) angle = math.acos((radius - arc_height) / radius) * sign + Arc.__init__(self, angle=angle, radius=radius, **kwargs) + else: + Arc.__init__(self, angle=angle, radius=1.0, **kwargs) - Arc.__init__(self, angle=angle, **kwargs) if angle == 0: self.set_points_as_corners([LEFT, RIGHT]) self.put_start_and_end_on(start, end) @@ -359,10 +369,8 @@ def __init__(self, start_point, end_point, **kwargs): class Circle(Arc): - CONFIG = {"color": RED, "close_new_points": True, "anchors_span_full_range": False} - - def __init__(self, **kwargs): - Arc.__init__(self, 0, TAU, **kwargs) + def __init__(self, color=RED, **kwargs): + Arc.__init__(self, start_angle=0, angle=TAU, color=color, **kwargs) def surround(self, mobject, dim_to_match=0, stretch=False, buffer_factor=1.2): # Ignores dim_to_match and stretch; result will always be a circle @@ -382,36 +390,33 @@ def point_at_angle(self, angle): class Dot(Circle): CONFIG = { - "radius": DEFAULT_DOT_RADIUS, "stroke_width": 0, "fill_opacity": 1.0, - "color": WHITE, } - def __init__(self, point=ORIGIN, **kwargs): - Circle.__init__(self, arc_center=point, **kwargs) + def __init__(self, point=ORIGIN, radius=DEFAULT_DOT_RADIUS, color=WHITE, **kwargs): + Circle.__init__(self, arc_center=point, radius=radius, color=color, **kwargs) class SmallDot(Dot): - """ - A dot with small radius - """ + """A dot with small radius.""" - CONFIG = {"radius": DEFAULT_SMALL_DOT_RADIUS} + def __init__(self, radius=DEFAULT_SMALL_DOT_RADIUS, **kwargs): + Dot.__init__(self, radius=radius, **kwargs) class AnnotationDot(Dot): - """ - A dot with bigger radius and bold stroke to annotate scenes. - """ + """A dot with big radius and bold stroke to annotate scenes.""" CONFIG = { - "radius": DEFAULT_DOT_RADIUS * 1.3, "stroke_width": 5, "stroke_color": WHITE, "fill_color": BLUE, } + def __init__(self, radius=DEFAULT_DOT_RADIUS * 1.3, **kwargs): + Dot.__init__(self, radius=radius, **kwargs) + class LabeledDot(Dot): """A :class:`Dot` containing a label in its center. @@ -467,25 +472,33 @@ def __init__(self, label, radius=None, **kwargs) -> None: class Ellipse(Circle): - CONFIG = {"width": 2, "height": 1} - - def __init__(self, **kwargs): - Circle.__init__(self, **kwargs) - self.set_width(self.width, stretch=True) - self.set_height(self.height, stretch=True) + def __init__(self, width=2.0, height=1.0, **kwargs): + self.width = width + self.height = height + Circle.__init__(self, width=width, height=height, **kwargs) + self.set_width(width, stretch=True) + self.set_height(height, stretch=True) class AnnularSector(Arc): CONFIG = { - "inner_radius": 1, - "outer_radius": 2, - "angle": TAU / 4, - "start_angle": 0, "fill_opacity": 1, "stroke_width": 0, - "color": WHITE, } + def __init__( + self, + angle=TAU / 4, + start_angle=0, + outer_radius=2.0, + inner_radius=1.0, + color=WHITE, + **kwargs + ): + self.inner_radius = inner_radius + self.outer_radius = outer_radius + Arc.__init__(self, angle=angle, start_angle=start_angle, color=color, **kwargs) + def generate_points(self): inner_arc, outer_arc = [ Arc( @@ -504,19 +517,31 @@ def generate_points(self): class Sector(AnnularSector): - CONFIG = {"outer_radius": 1, "inner_radius": 0} + def __init__(self, outer_radius=1.0, inner_radius=0.0, color=WHITE, **kwargs): + AnnularSector.__init__( + self, + outer_radius=outer_radius, + inner_radius=inner_radius, + color=color, + **kwargs + ) class Annulus(Circle): CONFIG = { - "inner_radius": 1, - "outer_radius": 2, "fill_opacity": 1, "stroke_width": 0, - "color": WHITE, - "mark_paths_closed": False, } + def __init__(self, outer_radius=2.0, inner_radius=1.0, color=WHITE, **kwargs): + Circle.__init__( + self, + outer_radius=outer_radius, + inner_radius=inner_radius, + color=color, + **kwargs + ) + def generate_points(self): self.radius = self.outer_radius outer_circle = Circle(radius=self.outer_radius) @@ -528,9 +553,9 @@ def generate_points(self): class Line(TipableVMobject): - CONFIG = {"buff": 0, "path_arc": None} # angle of arc specified here - - def __init__(self, start=LEFT, end=RIGHT, **kwargs): + def __init__(self, start=LEFT, end=RIGHT, path_arc=None, buff=0, **kwargs): + self.path_arc = path_arc + self.buff = buff digest_config(self, kwargs) self.set_start_and_end_attrs(start, end) VMobject.__init__(self, **kwargs) @@ -622,14 +647,17 @@ def set_opacity(self, opacity, family=True): class DashedLine(Line): - CONFIG = { - "dash_length": DEFAULT_DASH_LENGTH, - "dash_spacing": None, - "positive_space_ratio": 0.5, - } - - def __init__(self, *args, **kwargs): - Line.__init__(self, *args, **kwargs) + def __init__( + self, + dash_length=DEFAULT_DASH_LENGTH, + dash_spacing=None, + positive_space_ratio=0.5, + **kwargs + ): + self.dash_length = dash_length + self.dash_spacing = dash_spacing + self.positive_space_ratio = positive_space_ratio + Line.__init__(self, **kwargs) ps_ratio = self.positive_space_ratio num_dashes = self.calculate_num_dashes(ps_ratio) dashes = DashedVMobject( @@ -668,9 +696,9 @@ def get_last_handle(self): class TangentLine(Line): - CONFIG = {"length": 1, "d_alpha": 1e-6} - - def __init__(self, vmob, alpha, **kwargs): + def __init__(self, vmob, alpha, length=1, d_alpha=1e-6, **kwargs): + self.length = length + self.d_alpha = d_alpha digest_config(self, kwargs) da = self.d_alpha a1 = np.clip(alpha - da, 0, 1) @@ -682,9 +710,9 @@ def __init__(self, vmob, alpha, **kwargs): class Elbow(VMobject): - CONFIG = {"width": 0.2, "angle": 0} - - def __init__(self, **kwargs): + def __init__(self, width=0.2, angle=0, **kwargs): + self.width = width + self.angle = angle VMobject.__init__(self, **kwargs) self.set_points_as_corners([UP, UP + RIGHT, RIGHT]) self.set_width(self.width, about_point=ORIGIN) @@ -692,16 +720,19 @@ def __init__(self, **kwargs): class Arrow(Line): - CONFIG = { - "stroke_width": 6, - "buff": MED_SMALL_BUFF, - "max_tip_length_to_length_ratio": 0.25, - "max_stroke_width_to_length_ratio": 5, - "preserve_tip_size_when_scaling": True, - } - - def __init__(self, *args, **kwargs): - Line.__init__(self, *args, **kwargs) + def __init__( + self, + stroke_width=6, + buff=MED_SMALL_BUFF, + preserve_tip_size_when_scaling=True, + max_stroke_width_to_length_ratio=5, + max_tip_length_to_length_ratio=0.25, + **kwargs + ): + self.preserve_tip_size_when_scaling = preserve_tip_size_when_scaling + self.max_stroke_width_to_length_ratio = max_stroke_width_to_length_ratio + self.max_tip_length_to_length_ratio = max_tip_length_to_length_ratio + Line.__init__(self, stroke_width=stroke_width, buff=buff, **kwargs) # TODO, should this be affected when # Arrow.set_stroke is called? self.initial_stroke_width = self.stroke_width @@ -719,7 +750,7 @@ def scale(self, factor, scale_tips=False, **kwargs): -------- :: - >>> arrow = Arrow(np.array([-1, -1, 0]), np.array([1, 1, 0]), buff=0) + >>> arrow = Arrow(start=np.array([-1, -1, 0]), end=np.array([1, 1, 0]), buff=0) >>> scaled_arrow = arrow.scale(2) >>> scaled_arrow.get_start_and_end() (array([-2., -2., 0.]), array([2., 2., 0.])) @@ -729,7 +760,7 @@ def scale(self, factor, scale_tips=False, **kwargs): Manually scaling the object using the default method :meth:`~.Mobject.scale` does not have the same properties:: - >>> new_arrow = Arrow(np.array([-1, -1, 0]), np.array([1, 1, 0]), buff=0) + >>> new_arrow = Arrow(start=np.array([-1, -1, 0]), end=np.array([1, 1, 0]), buff=0) >>> another_scaled_arrow = VMobject.scale(new_arrow, 2) >>> another_scaled_arrow.tip.tip_length == arrow.tip.tip_length False @@ -778,19 +809,18 @@ def set_stroke_width_from_length(self): class Vector(Arrow): - CONFIG = {"buff": 0} - - def __init__(self, direction=RIGHT, **kwargs): + def __init__(self, direction=RIGHT, buff=0, **kwargs): + self.buff = buff if len(direction) == 2: direction = np.append(np.array(direction), 0) - Arrow.__init__(self, ORIGIN, direction, **kwargs) + Arrow.__init__(self, start=ORIGIN, end=direction, buff=buff, **kwargs) class DoubleArrow(Arrow): - def __init__(self, *args, **kwargs): + def __init__(self, **kwargs): if "tip_shape_end" in kwargs: kwargs["tip_shape"] = kwargs.pop("tip_shape_end") - Arrow.__init__(self, *args, **kwargs) + Arrow.__init__(self, **kwargs) self.add_tip( at_start=True, tip_shape=kwargs.get("tip_shape_start", ArrowTriangleFilledTip), @@ -804,10 +834,8 @@ def __init__(self, points, **kwargs): class Polygon(VMobject): - CONFIG = {"color": BLUE} - - def __init__(self, *vertices, **kwargs): - VMobject.__init__(self, **kwargs) + def __init__(self, *vertices, color=BLUE, **kwargs): + VMobject.__init__(self, color=color, **kwargs) self.set_points_as_corners([*vertices, vertices[0]]) def get_vertices(self): @@ -849,9 +877,8 @@ def round_corners(self, radius=0.5): class RegularPolygon(Polygon): - CONFIG = {"start_angle": None} - - def __init__(self, n=6, **kwargs): + def __init__(self, n=6, start_angle=None, **kwargs): + self.start_angle = start_angle digest_config(self, kwargs, locals()) if self.start_angle is None: if n % 2 == 0: @@ -869,36 +896,26 @@ def __init__(self, **kwargs): class Rectangle(Polygon): - CONFIG = { - "color": WHITE, - "height": 2.0, - "width": 4.0, - "mark_paths_closed": True, - "close_new_points": True, - } - - def __init__(self, **kwargs): - Polygon.__init__(self, UL, UR, DR, DL, **kwargs) + def __init__(self, height=2.0, width=4.0, color=WHITE, **kwargs): + Polygon.__init__( + self, UL, UR, DR, DL, height=height, width=width, color=color, **kwargs + ) self.set_width(self.width, stretch=True) self.set_height(self.height, stretch=True) class Square(Rectangle): - CONFIG = {"side_length": 2.0} - - def __init__(self, **kwargs): - digest_config(self, kwargs) + def __init__(self, side_length=2.0, **kwargs): + self.side_length = side_length Rectangle.__init__( self, height=self.side_length, width=self.side_length, **kwargs ) class RoundedRectangle(Rectangle): - CONFIG = {"corner_radius": 0.5} - - def __init__(self, **kwargs): - Rectangle.__init__(self, **kwargs) - self.round_corners(self.corner_radius) + def __init__(self, corner_radius=0.5, **kwargs): + Rectangle.__init__(self, corner_radius=corner_radius, **kwargs) + self.round_corners(corner_radius) class ArrowTip(VMobject): @@ -932,7 +949,7 @@ class ArrowTip(VMobject): ... RegularPolygon.__init__(self, n=5, **kwargs) ... self.set_width(self.length) ... self.set_height(self.length, stretch=True) - >>> arr = Arrow(np.array([-2, -2, 0]), np.array([2, 2, 0]), + >>> arr = Arrow(start=np.array([-2, -2, 0]), end=np.array([2, 2, 0]), ... tip_shape=MyCustomArrowTip) >>> isinstance(arr.tip, RegularPolygon) True @@ -944,7 +961,7 @@ class ArrowTip(VMobject): Using a class inherited from :class:`ArrowTip` to get a non-filled tip is a shorthand to manually specifying the arrow tip style as follows:: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([1, 1, 0]), + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([1, 1, 0]), ... tip_style={'fill_opacity': 0, 'stroke_width': 3}) @@ -969,7 +986,7 @@ def base(self): -------- :: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([2, 0, 0]), buff=0) + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([2, 0, 0]), buff=0) >>> arrow.tip.base.round(2) + 0. # add 0. to avoid negative 0 in output array([1.65, 0. , 0. ]) @@ -984,7 +1001,7 @@ def tip_point(self): -------- :: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([2, 0, 0]), buff=0) + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([2, 0, 0]), buff=0) >>> arrow.tip.tip_point.round(2) + 0. array([2., 0., 0.]) @@ -999,7 +1016,7 @@ def vector(self): -------- :: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([2, 2, 0]), buff=0) + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([2, 2, 0]), buff=0) >>> arrow.tip.vector.round(2) + 0. array([0.25, 0.25, 0. ]) @@ -1014,7 +1031,7 @@ def tip_angle(self): -------- :: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([1, 1, 0]), buff=0) + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([1, 1, 0]), buff=0) >>> round(arrow.tip.tip_angle, 5) == round(PI/4, 5) True @@ -1029,7 +1046,7 @@ def tip_length(self): -------- :: - >>> arrow = Arrow(np.array([0, 0, 0]), np.array([1, 2, 0])) + >>> arrow = Arrow(start=np.array([0, 0, 0]), end=np.array([1, 2, 0])) >>> round(arrow.tip.tip_length, 3) 0.35 diff --git a/manim/mobject/mobject.py b/manim/mobject/mobject.py index 4bd94de859..c284b9dbc4 100644 --- a/manim/mobject/mobject.py +++ b/manim/mobject/mobject.py @@ -42,15 +42,12 @@ class Mobject(Container): """ - CONFIG = { - "color": WHITE, - "name": None, - "dim": 3, - "target": None, - "z_index": 0, - } - - def __init__(self, **kwargs): + def __init__(self, color=WHITE, name=None, dim=3, target=None, z_index=0, **kwargs): + self.color = color + self.name = name + self.dim = dim + self.target = target + self.z_index = z_index Container.__init__(self, **kwargs) self.point_hash = None self.submobjects = [] diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py index 8f7546cfe7..5cfe5395d3 100644 --- a/manim/mobject/types/vectorized_mobject.py +++ b/manim/mobject/types/vectorized_mobject.py @@ -34,8 +34,6 @@ # TODO # - Change cubic curve groups to have 4 points instead of 3 # - Change sub_path idea accordingly -# - No more mark_paths_closed, instead have the camera test -# if last point in close to first point # - Think about length of self.points. Always 0 or 1 mod 4? # That's kind of weird. @@ -59,9 +57,6 @@ class VMobject(Mobject): # secondary color in the direction of sheen_direction. "sheen_factor": 0.0, "sheen_direction": UL, - # Indicates that it will not be displayed, but - # that it should count in parent mobject's path - "close_new_points": False, "pre_function_handle_to_anchor_scale_factor": 0.01, "make_smooth_after_applying_functions": False, "background_image_file": None, diff --git a/tests/control_data/graphical_units_data/geometry/ArrowTest.npz b/tests/control_data/graphical_units_data/geometry/ArrowTest.npz new file mode 100644 index 0000000000..82f4f26c02 Binary files /dev/null and b/tests/control_data/graphical_units_data/geometry/ArrowTest.npz differ diff --git a/tests/control_data/graphical_units_data/geometry/CustomDashedLineTest.npz b/tests/control_data/graphical_units_data/geometry/CustomDashedLineTest.npz new file mode 100644 index 0000000000..32560eb924 Binary files /dev/null and b/tests/control_data/graphical_units_data/geometry/CustomDashedLineTest.npz differ diff --git a/tests/control_data/graphical_units_data/geometry/DashedLineTest.npz b/tests/control_data/graphical_units_data/geometry/DashedLineTest.npz new file mode 100644 index 0000000000..ea58c016ff Binary files /dev/null and b/tests/control_data/graphical_units_data/geometry/DashedLineTest.npz differ diff --git a/tests/control_data/graphical_units_data/geometry/SmallDotTest.npz b/tests/control_data/graphical_units_data/geometry/SmallDotTest.npz new file mode 100644 index 0000000000..182b8bc2a8 Binary files /dev/null and b/tests/control_data/graphical_units_data/geometry/SmallDotTest.npz differ diff --git a/tests/control_data/graphical_units_data/geometry/TangentLineTest.npz b/tests/control_data/graphical_units_data/geometry/TangentLineTest.npz new file mode 100644 index 0000000000..bb18236163 Binary files /dev/null and b/tests/control_data/graphical_units_data/geometry/TangentLineTest.npz differ diff --git a/tests/test_graphical_units/test_geometry.py b/tests/test_graphical_units/test_geometry.py index 86720b603a..aa3f8f848c 100644 --- a/tests/test_graphical_units/test_geometry.py +++ b/tests/test_graphical_units/test_geometry.py @@ -13,7 +13,7 @@ def construct(self): class ArcTest(Scene): def construct(self): - a = Arc(PI) + a = Arc(start_angle=PI) self.play(Animation(a)) @@ -23,6 +23,18 @@ def construct(self): self.play(Animation(a)) +class ArrowTest(Scene): + def construct(self): + a = Arrow() + self.play(Animation(a)) + b = ( + Arrow(color=RED, stroke_width=12, end=2 * LEFT, start=2 * RIGHT) + .scale(2) + .shift(UP) + ) + self.play(Animation(b)) + + class CurvedArrowTest(Scene): def construct(self): a = CurvedArrow(np.array([1, 1, 0]), np.array([2, 2, 0])) @@ -34,8 +46,8 @@ def construct(self): from manim.mobject.geometry import ArrowCircleTip, ArrowSquareFilledTip a = DoubleArrow( - np.array([-1, -1, 0]), - np.array([1, 1, 0]), + start=np.array([-1, -1, 0]), + end=np.array([1, 1, 0]), tip_shape_start=ArrowCircleTip, tip_shape_end=ArrowSquareFilledTip, ) @@ -48,6 +60,14 @@ def construct(self): self.play(Animation(circle)) +class SmallDotTest(Scene): + def construct(self): + a = SmallDot() + self.play(Animation(a)) + b = SmallDot(color=BLUE).shift(UP) + self.play(Animation(b)) + + class DotTest(Scene): def construct(self): dot = Dot() @@ -90,6 +110,32 @@ def construct(self): self.play(Animation(a)) +class DashedLineTest(Scene): + def construct(self): + a = DashedLine() + self.play(Animation(a)) + + +class CustomDashedLineTest(Scene): + def construct(self): + a = DashedLine(end=2 * DOWN, start=2 * UP, dash_length=0.5) + self.play(Animation(a)) + + +class TangentLineTest(Scene): + def construct(self): + circle = Circle(color=WHITE) + self.add(circle) + t1 = TangentLine(circle, 0.5, color=BLUE) + self.play(Animation(t1)) + t2 = TangentLine(circle, 1.0, length=2, color=RED) + self.play(Animation(t2)) + t3 = TangentLine(circle, 0.75, length=5, d_alpha=0.1, color=YELLOW) + self.play(Animation(t3)) + t4 = TangentLine(circle, 0.321, length=1.5, d_alpha=-0.1, color=GREEN) + self.play(Animation(t4)) + + class Elbowtest(Scene): def construct(self): a = Elbow() diff --git a/tests/test_scene_rendering/test_caching_related.py b/tests/test_scene_rendering/test_caching_related.py index 03395571ab..2d7ddc7084 100644 --- a/tests/test_scene_rendering/test_caching_related.py +++ b/tests/test_scene_rendering/test_caching_related.py @@ -6,6 +6,7 @@ from ..utils.video_tester import * +@pytest.mark.slow @video_comparison( "SceneWithMultipleWaitCallsWithNFlag.json", "videos/simple_scenes/480p15/SceneWithMultipleWaitCalls.mp4", @@ -29,6 +30,7 @@ def test_wait_skip(tmp_path, manim_cfg_file, simple_scenes_path): assert exit_code == 0, err +@pytest.mark.slow @video_comparison( "SceneWithMultiplePlayCallsWithNFlag.json", "videos/simple_scenes/480p15/SceneWithMultipleCalls.mp4",