def test_mobject_add(): """Test Mobject.add().""" obj = Mobject() container_add(obj, lambda: obj.submobjects) # a Mobject cannot contain itself with pytest.raises(ValueError): obj.add(obj)
def test_vgroup_init(): """Test the VGroup instantiation.""" VGroup() VGroup(VMobject()) VGroup(VMobject(), VMobject()) with pytest.raises(TypeError): VGroup(Mobject()) with pytest.raises(TypeError): VGroup(Mobject(), Mobject())
def test_overlapping_family(): """Check that each member of the family is only gathered once.""" mob, child1, child2, = Mobject(), Mobject(), Mobject(), gchild1, gchild2, gchild_common = Mobject(), Mobject(), Mobject() child1.add(gchild1, gchild_common) child2.add(gchild2, gchild_common) mob.add(child1, child2) family = mob.get_family() assert mob in family assert len(family) == 6 assert family.count(gchild_common) == 1
def container_remove(obj, get_submobjects): """Call this function with a Container instance to test its remove() method.""" to_remove = Mobject() obj.add(to_remove) obj.add(*(Mobject() for _ in range(10))) assert len(get_submobjects()) == 11 obj.remove(to_remove) assert len(get_submobjects()) == 10 obj.remove(to_remove) assert len(get_submobjects()) == 10 # check that Container.remove() returns the instance (for chained calls) assert obj.add(Mobject()) is obj
def test_vgroup_add(): """Test the VGroup add method.""" obj = VGroup() assert len(obj.submobjects) == 0 obj.add(VMobject()) assert len(obj.submobjects) == 1 with pytest.raises(TypeError): obj.add(Mobject()) assert len(obj.submobjects) == 1 with pytest.raises(TypeError): # If only one of the added object is not an instance of VMobject, none of them should be added obj.add(VMobject(), Mobject()) assert len(obj.submobjects) == 1 with pytest.raises(ValueError): # a Mobject cannot contain itself obj.add(obj)
def test_bracelabel_copy(tmp_path): """Test that a copy is a deepcopy.""" # For this test to work, we need to tweak some folders temporarily original_text_dir = file_writer_config["text_dir"] original_tex_dir = file_writer_config["tex_dir"] mediadir = os.path.join(tmp_path, "deepcopy") file_writer_config["text_dir"] = os.path.join(mediadir, "Text") file_writer_config["tex_dir"] = os.path.join(mediadir, "Tex") for el in ["text_dir", "tex_dir"]: os.makedirs(file_writer_config[el]) # Before the refactoring of Mobject.copy(), the class BraceLabel was the # only one to have a non-trivial definition of copy. Here we test that it # still works after the refactoring. orig = BraceLabel(Mobject(), "label") copy = orig.copy() assert orig is orig assert orig is not copy assert orig.brace is not copy.brace assert orig.label is not copy.label assert orig.submobjects is not copy.submobjects assert orig.submobjects[0] is orig.brace assert copy.submobjects[0] is copy.brace assert orig.submobjects[0] is not copy.brace assert copy.submobjects[0] is not orig.brace # Restore the original folders file_writer_config["text_dir"] = original_text_dir file_writer_config["tex_dir"] = original_tex_dir
def test_bracelabel_copy(tmp_path): """Test that a copy is a deepcopy.""" # For this test to work, we need to tweak some folders temporarily original_text_dir = config["text_dir"] original_tex_dir = config["tex_dir"] mediadir = Path(tmp_path) / "deepcopy" config["text_dir"] = str(mediadir.joinpath("Text")) config["tex_dir"] = str(mediadir.joinpath("Tex")) for el in ["text_dir", "tex_dir"]: Path(config[el]).mkdir(parents=True) # Before the refactoring of Mobject.copy(), the class BraceLabel was the # only one to have a non-trivial definition of copy. Here we test that it # still works after the refactoring. orig = BraceLabel(Mobject(), "label") copy = orig.copy() assert orig is orig assert orig is not copy assert orig.brace is not copy.brace assert orig.label is not copy.label assert orig.submobjects is not copy.submobjects assert orig.submobjects[0] is orig.brace assert copy.submobjects[0] is copy.brace assert orig.submobjects[0] is not copy.brace assert copy.submobjects[0] is not orig.brace # Restore the original folders config["text_dir"] = original_text_dir config["tex_dir"] = original_tex_dir
def test_vdict_add(): """Test the VDict add method.""" obj = VDict() assert len(obj.submob_dict) == 0 obj.add([("a", VMobject())]) assert len(obj.submob_dict) == 1 with pytest.raises(TypeError): obj.add([("b", Mobject())])
def test_ABC(): """Test that the Container class cannot be instantiated.""" with pytest.raises(TypeError): Container() # The following should work without raising exceptions Mobject() Scene()
def test_scene_add_remove(): with tempconfig({"dry_run": True}): scene = Scene() assert len(scene.mobjects) == 0 scene.add(Mobject()) assert len(scene.mobjects) == 1 scene.add(*(Mobject() for _ in range(10))) assert len(scene.mobjects) == 11 # Check that adding a mobject twice does not actually add it twice repeated = Mobject() scene.add(repeated) assert len(scene.mobjects) == 12 scene.add(repeated) assert len(scene.mobjects) == 12 # Check that Scene.add() returns the Scene (for chained calls) assert scene.add(Mobject()) is scene to_remove = Mobject() scene = Scene() scene.add(to_remove) scene.add(*(Mobject() for _ in range(10))) assert len(scene.mobjects) == 11 scene.remove(to_remove) assert len(scene.mobjects) == 10 scene.remove(to_remove) assert len(scene.mobjects) == 10 # Check that Scene.remove() returns the instance (for chained calls) assert scene.add(Mobject()) is scene
def test_vgroup_add_dunder(): """Test the VGroup __add__ magic method.""" obj = VGroup() assert len(obj.submobjects) == 0 obj + VMobject() assert len(obj.submobjects) == 0 obj += VMobject() assert len(obj.submobjects) == 1 with pytest.raises(TypeError): obj += Mobject() assert len(obj.submobjects) == 1 with pytest.raises(TypeError): # If only one of the added object is not an instance of VMobject, none of them should be added obj += (VMobject(), Mobject()) assert len(obj.submobjects) == 1 with pytest.raises(Exception): # TODO change this to ValueError once #307 is merged # a Mobject cannot contain itself obj += obj
def container_add(obj, get_submobjects): """Call this function with a Container instance to test its add() method.""" # check that obj.submobjects is updated correctly assert len(get_submobjects()) == 0 obj.add(Mobject()) assert len(get_submobjects()) == 1 obj.add(*(Mobject() for _ in range(10))) assert len(get_submobjects()) == 11 # check that adding a mobject twice does not actually add it twice repeated = Mobject() obj.add(repeated) assert len(get_submobjects()) == 12 obj.add(repeated) assert len(get_submobjects()) == 12 # check that Container.add() returns the Mobject (for chained calls) assert obj.add(Mobject()) is obj
def test_mobject_inheritance(): mob = Mobject() a = MobjectA() b = MobjectB() c = MobjectC() assert type(AnimationA1(mob)) is AnimationA1 assert type(AnimationA1(a)) is AnimationA2 assert type(AnimationA1(b)) is AnimationA2 assert type(AnimationA1(c)) is AnimationA3
def test_set_color(): m = Mobject() assert m.color.hex == "#fff" m.set_color(BLACK) assert m.color.hex == "#000" m = VMobject() assert m.color.hex == "#fff" m.set_color(BLACK) assert m.color.hex == "#000"
def test_vdict_init(): """Test the VDict instantiation.""" # Test empty VDict VDict() # Test VDict made from list of pairs VDict([("a", VMobject()), ("b", VMobject()), ("c", VMobject())]) # Test VDict made from a python dict VDict({"a": VMobject(), "b": VMobject(), "c": VMobject()}) # Test VDict made using zip VDict(zip(["a", "b", "c"], [VMobject(), VMobject(), VMobject()])) # If the value is of type Mobject, must raise a TypeError with pytest.raises(TypeError): VDict({"a": Mobject()})
def test_mobject_copy(): """Test that a copy is a deepcopy.""" orig = Mobject() orig.add(*[Mobject() for _ in range(10)]) copy = orig.copy() assert orig is orig assert orig is not copy assert orig.submobjects is not copy.submobjects for i in range(10): assert orig.submobjects[i] is not copy.submobjects[i]
def add( self, name: str, val_orig: m.Mobject, highlight: m.Mobject = None, extra_animations: List[m.Animation] = None, ) -> None: """Add an name-value association to the context and return animations name -- the name part of the association val_orig -- a Mobject that is the value part of the association highlight -- a Mobject that should be highlighted before other animations are played, defaults to val_orig extra_animations -- animations to be played along the others during the last step This creates a copy of `val_orig` and sets its target to the position of the value in the context, the returned animations will make the copy move to this position. """ self.scene.play(m.Indicate(highlight if highlight else val_orig)) association = m.VDict([ ("name", m.Tex(name, color=m.GRAY)), ("eq", m.Tex("=", color=m.GRAY)), ("val", val_orig.copy()), ]) association["name"].move_to(self.entries[-1], aligned_edge=m.LEFT) association["eq"].next_to(association["name"], m.RIGHT) association["val"].generate_target().next_to(association["eq"], m.RIGHT).set_color(m.GRAY) self.scene.play( m.ApplyMethod(self.entries.shift, m.UP * 0.5), m.FadeInFrom(association["name"], direction=m.DOWN), m.FadeInFrom(association["eq"], direction=m.DOWN), m.MoveToTarget(association["val"]), *(extra_animations if extra_animations else []), ) self.entries.add(association) self.scene.remove(association) # The previous line created a copy
def test_mobject_add(): """Test Mobject.add().""" """Call this function with a Container instance to test its add() method.""" # check that obj.submobjects is updated correctly obj = Mobject() assert len(obj.submobjects) == 0 obj.add(Mobject()) assert len(obj.submobjects) == 1 obj.add(*(Mobject() for _ in range(10))) assert len(obj.submobjects) == 11 # check that adding a mobject twice does not actually add it twice repeated = Mobject() obj.add(repeated) assert len(obj.submobjects) == 12 obj.add(repeated) assert len(obj.submobjects) == 12 # check that Mobject.add() returns the Mobject (for chained calls) assert obj.add(Mobject()) is obj obj = Mobject() # a Mobject cannot contain itself with pytest.raises(ValueError): obj.add(obj) # can only add Mobjects with pytest.raises(TypeError): obj.add("foo")
def __init__(self, source: Mobject, target: Mobject, label: Optional[str] = None, font=DEFAULT_FONT, **kwargs): """ Args: source: The source object target: The target object label: The optional label text to put over the arrow """ super().__init__(**kwargs) self.font = font label_direction = UP label_buff = 0 arrow: Optional[Arrow] = None if source.get_x(RIGHT) <= target.get_x(LEFT): arrow = Arrow(start=source.get_edge_center(RIGHT), end=target.get_edge_center(LEFT), buff=0) label_direction = UP elif source.get_x(LEFT) >= target.get_x(RIGHT): arrow = Arrow(start=source.get_edge_center(LEFT), end=target.get_edge_center(RIGHT), buff=0) label_direction = UP elif source.get_y(DOWN) >= target.get_y(UP): arrow = Arrow(start=source.get_edge_center(DOWN), end=target.get_edge_center(UP), buff=0) label_direction = RIGHT label_buff = VERTICAL_ARROW_LABEL_BUFF elif source.get_y(UP) <= target.get_y(DOWN): arrow = Arrow(start=source.get_edge_center(UP), end=target.get_edge_center(DOWN), buff=0) label_direction = RIGHT label_buff = VERTICAL_ARROW_LABEL_BUFF if not arrow: raise ValueError("Unable to connect") self.add(arrow) if label: text = Text(label, font=self.font, size=0.5, slant=ITALIC) text.next_to(arrow, direction=label_direction, buff=label_buff) self.add(text)
def change_label(self, mob: Mobject) -> Mobject: mob.scale(self.font_scale) mob.move_to(self.value['label'].get_center()) return self.value['label'].animate.become(mob)
def test_mobject_remove(): """Test Mobject.remove().""" obj = Mobject() container_remove(obj, lambda: obj.submobjects)
def test_mobject_remove(): """Test Mobject.remove().""" obj = Mobject() to_remove = Mobject() obj.add(to_remove) obj.add(*(Mobject() for _ in range(10))) assert len(obj.submobjects) == 11 obj.remove(to_remove) assert len(obj.submobjects) == 10 obj.remove(to_remove) assert len(obj.submobjects) == 10 assert obj.remove(Mobject()) is obj
def connect(self, source: Mobject, target: Mobject, label: Optional[str] = None) -> Connection: result = Connection() label_direction = UP label_buff = 0 arrow: Optional[Arrow] = None if source.get_x(RIGHT) <= target.get_x(LEFT): arrow = Arrow(start=source.get_edge_center(RIGHT), end=target.get_edge_center(LEFT), buff=0) label_direction = UP elif source.get_x(LEFT) >= target.get_x(RIGHT): arrow = Arrow(start=source.get_edge_center(LEFT), end=target.get_edge_center(RIGHT), buff=0) label_direction = UP elif source.get_y(DOWN) >= target.get_y(UP): arrow = Arrow(start=source.get_edge_center(DOWN), end=target.get_edge_center(UP), buff=0) label_direction = RIGHT label_buff = VERTICAL_ARROW_LABEL_BUFF elif source.get_y(UP) <= target.get_y(DOWN): arrow = Arrow(start=source.get_edge_center(UP), end=target.get_edge_center(DOWN), buff=0) label_direction = RIGHT label_buff = VERTICAL_ARROW_LABEL_BUFF if not arrow: raise ValueError("Unable to connect") result.add(arrow) if label: text = Text(label, font=self.text_font, size=0.7, slant=ITALIC) text.next_to(arrow, direction=label_direction, buff=label_buff) result.add(text) return result
def test_family(): """Check that the family is gathered correctly.""" # Check that an empty mobject's family only contains itself mob = Mobject() assert mob.get_family() == [mob] # Check that all children are in the family mob = Mobject() children = [Mobject() for _ in range(10)] mob.add(*children) family = mob.get_family() assert len(family) == 1 + 10 assert mob in family for c in children: assert c in family # Nested children should be in the family mob = Mobject() grandchildren = {} for _ in range(10): child = Mobject() grandchildren[child] = [Mobject() for _ in range(10)] child.add(*grandchildren[child]) mob.add(*list(grandchildren.keys())) family = mob.get_family() assert len(family) == 1 + 10 + 10 * 10 assert mob in family for c in grandchildren: assert c in family for gc in grandchildren[c]: assert gc in family
def eval_call(self, call: m.Mobject, val: int, val_mobject: m.Mobject) -> Tuple[int, m.Mobject]: """Show the evaluation of one function call, recursively call: the manim object representing the call, expected to be oneline val: the value of `n` for that call Returns the value and the corresponding Mobject of the result of the call """ context = CallContext(call, self) context.add("n", val_mobject) self.wait() # Evaluate the if's condition context.replace_occurrence(-1, call["if"]["cond"]["n"]) self.wait() replace_expr(self, call["if"]["cond"], f"\\verb|{str(val == 0).lower()}|") # Replace the expression with the correct if branch self.wait() self.play(m.Indicate(call["if"]["cond"])) if val == 0: bad = "rec" good = "base" else: good = "rec" bad = "base" strike_through = ThinLine( call[bad].get_corner(m.DL) + m.DL * 0.1, call[bad].get_corner(m.UR) + m.UR * 0.1, ) rect = ThinRectangle().surround(call[good], stretch=True) self.play(m.ShowCreation(strike_through), m.ShowCreation(rect)) self.wait() self.play( *map(m.FadeOut, [strike_through, rect, call["if"], call[bad], call["else"]])) self.play( m.ApplyMethod(call[good].next_to, call.get_corner(m.LEFT), m.RIGHT * 0.1)) if val == 0: # end of recursion self.play(*map(m.FadeOut, context.entries)) return 1, call["base"] # Evaluate the expression containing the recursive call up to the call, starting # by the right-hand operand of the multiplication self.wait() self.play(m.Indicate(call["rec"]["call"])) context.replace_occurrence(-1, call["rec"]["call"]["arg"]["n"]) self.wait(m.DEFAULT_WAIT_TIME / 2) replace_expr(self, call["rec"]["call"]["arg"], f"\\verb|{val - 1}|", aligned_edge=m.LEFT) # Evaluate the recursive call rect = ThinRectangle(color=m.BLUE).surround(call["rec"]["call"], stretch=True) def_instance = deepcopy(self.def_box["function"]).remove("fn") def_instance.generate_target().scale(1 / self.def_scale_ratio).next_to( rect, m.RIGHT * 5) lines = m.VGroup( ThinLine( rect.get_corner(m.RIGHT), def_instance.target.get_corner(m.LEFT) + m.LEFT * 0.2, color=m.BLUE, ), ThinLine( def_instance.target.get_corner(m.DL) + m.LEFT * 0.2, def_instance.target.get_corner(m.UL) + m.LEFT * 0.2, color=m.BLUE, ), ) self.play(m.ShowCreation(rect)) self.wait(m.DEFAULT_WAIT_TIME / 2) self.play(m.Indicate(self.def_box)) self.play(m.MoveToTarget(def_instance), m.ShowCreation(lines)) self.wait() shift_vector = (call["rec"]["call"]["arg"].get_center() - val_mobject.get_center()) self.play( m.ApplyMethod(self.camera.frame.shift, shift_vector), m.ApplyMethod(self.def_box.shift, shift_vector), ) recursive_call_res, recursive_call_res_mobject = self.eval_call( def_instance, val - 1, call["rec"]["call"]["arg"]) self.play( m.ApplyMethod(self.camera.frame.shift, -shift_vector), m.ApplyMethod(self.def_box.shift, -shift_vector), ) self.play( m.FadeOut(lines), m.FadeOut(rect), m.FadeOut(call["rec"]["call"]), m.ApplyMethod(recursive_call_res_mobject.next_to, call["rec"]["*"], m.RIGHT), ) self.wait() call["rec"]["call"] = recursive_call_res_mobject # Evaluate the left-hand operand of the multiplication context.replace_occurrence(-1, call["rec"]["n"]) res = val * recursive_call_res replace_expr(self, call["rec"], f"\\verb|{res}|", aligned_edge=m.LEFT) self.play(*map(m.FadeOut, context.entries)) return res, call["rec"]