.. role:: underbold :class: underbold Basic attributes of Mobjects =============================== If you want to see a tree of what inheritance is like between Mobjects you can see the following diagram (created by the ManimCE community). .. image:: ../_static/images/mob_tree.svg Camera dimensions ------------------- Before studying the attributes of Mobjects it is necessary to know the basic characteristics of the camera. By default, the **Camera** is 8 units high, and since the aspect ratio is 16/9, the width can be easily calculated. These 8 units are independent of the rendering resolution (480p, 720p, etc), so there is no need to worry about that. .. image:: ../_static/images/camera_settings.png :align: center The coordinates of each Mobject are a three-dimensional array, and the coordinate :math:`[0,0,0]` is located in the center of the camera. .. image:: ../_static/images/camera_coordinates.png For now, we will not worry about the 3rd dimension, we will focus only on the x, y coordinates. .. note:: These values can be changed but it is not recommended. Basic attributes ------------------- All Mobjects have four main attributes: * Position * Width * Height * Z index We won't study **Z index** in this section until **Section 4**. To start studying the properties we will create a rectangle and add it on the screen, I recommend the student to use Jupyter-Notebook to study these first sections. We will omit the scene name to save space. .. code:: python def construct(self): req = Rectangle() self.add(req) .. image:: ../_static/images/cp1.png Position ------------------- Manim already has some constants that will help us locate our Mobjects, which are: :underbold:`Origin:` .. code:: python ORIGIN = np.array([ 0, 0, 0]) :underbold:`One-dimensional vectors:` .. code:: python UP = np.array([ 1, 0, 0]) DOWN = np.array([-1, 0, 0]) RIGHT = np.array([ 0, 1, 0]) LEFT = np.array([ 0,-1, 0]) :underbold:`Two-dimensional vectors:` .. code:: python UR = np.array([ 1, 1, 0]) UL = np.array([-1, 1, 0]) DR = np.array([ 1,-1, 0]) DL = np.array([-1,-1, 0]) When we instantiate a Mobject, it is always located by default in the center of the screen, that is, in the location :math:`[0,0,0]`. If we want to place this object in another position, there are two positioning systems: * **Absolute Position**: Use as reference the coordinates of the camera or the current location of our Mobject. * ``Mobject.move_to()`` * ``Mobject.shift()`` * **Relative Position**: We use another Mobject or coordinate as a reference to locate our Mobject. * ``Mobject.to_edge()`` * ``Mobject.to_corner()`` * ``Mobject.next_to()`` * ``Mobject.align_to()`` Absolute Position ------------------- Mobject.move_to() """""""""""""""""""""" This method requires a three-dimensional array (coordinate) to locate an object on the screen, **always use the center of the camera center as a reference**. .. warning:: Remember that if you place a Mobject outside the limits of the camera then your Mobject will not be visible in your animation, although Manim will have computed it. .. code:: python def construct(self): req = Rectangle() req.move_to([-3,2,0]) self.add(req) .. image:: ../_static/images/ap1.png In general, it is advisable to convert your coordinates to ``np.array``, or to use linear combinations of the **one-dimensional** or **two-dimensional** vectors to locate your objects. .. code:: python def construct(self): r = Rectangle() c = Circle() e = Ellipse() # Best practice r.move_to( np.array([-3, 2, 0]) ) # Other way c.move_to( LEFT * 3 + UP * 2 ) # Another way e.move_to( UL * 2 + LEFT ) self.add(r,c,e) .. image:: ../_static/images/ap2.png Mobject.shift() """""""""""""""""""""" This method is similar to ``move_to``, but the difference is that ``move_to`` :underbold:`always refers to the center of the camera` (the origin), while ``shift`` refers to the **current** position of your Mobject. To differentiate it, let's look at the following case: .. code:: python def construct(self): s = Square() c = Circle() # Apply move_to 4 times for _ in range(4): s.move_to(RIGHT) # Apply shift 4 times for _ in range(4): c.shift(RIGHT) self.add(s,c) .. image:: ../_static/images/ap3.png If we apply the same ``move_to`` 4 times, then it is redundant, because the movement always takes the **center** of the camera as a reference. But applying ``shift`` 4 times is different, because each shift takes the new Mobject coordinates as a reference. This can be made even clearer with an animation: .. code:: python def construct(self): s = Square() c = Circle() self.add(s,c) for _ in range(4): # Pause self.wait() # Move c.shift(RIGHT) s.move_to(RIGHT) .. raw:: html

You can notice that at the beginning both appear in the center of the camera, then the first cycle of the loop is applied and both move once to the right, but the second time only the circle (to whom the ``shift`` is applied) is it keeps moving, because the new ``shift`` (of the following loops) takes the new Mobject (circle) coordinates as a reference. Mobject.get... """""""""""""""""""""""" To obtain the coordinates of an object we can use the following getters: .. code:: python def construct(self): r = Rectangle() self.add(r) center = r.get_center() right = r.get_right() left = r.get_left() top = r.get_top() bottom = r.get_bottom() up_right = r.get_corner(UR) up_left = r.get_corner(UL) down_right = r.get_corner(DR) down_left = r.get_corner(DL) for n,p in zip( ["C" ,"R" ,"L" ,"T","B" ,"UR" ,"UL" ,"DR" ,"DL"], [center,right,left,top,bottom,up_right,up_left,down_right,down_left] ): t = Text(f"{n}",color=RED) t.move_to(p) self.add(t) .. image:: ../_static/images/ap5.png .. warning:: It is important to note that ``.get_center()`` :underbold:`does not get` the geometric center or center of mass of the Mobject. What ``.get_center()`` does is "create" an *imaginary* rectangle whose borders contain the entirety of your Mobject and then it returns the coordinates of that rectangle. If you need to obtain the center of mass of a Mobject use ``get_center_of_mass()``. Also exists: .. code:: python Mobject.get_x() # <==> Mobject.get_center()[0] Mobject.get_y() # <==> Mobject.get_center()[1] Mobject.get_z() # <==> Mobject.get_center()[2] # N is some real number Mobject.set_x(N) # <==> Mobject.move_to(RIGHT * N) Mobject.set_y(N) # <==> Mobject.move_to(UP * N) Mobject.set_z(N) # <==> Mobject.move_to(OUT * N) Relative Position ------------------- Mobject.to_edge() """""""""""""""""""""" This method moves vertically or horizontally to some edge of the camera, takes a **one-dimensional** vector as an argument and moves the Mobject in that direction to the edge. :underbold:`Examples`: .. code:: python def construct(self): req = Rectangle() req.to_edge(LEFT) self.add(req) .. image:: ../_static/images/cp2.png .. code:: python def construct(self): req = Rectangle() req.to_edge(UP) self.add(req) .. image:: ../_static/images/cp3.png We can even use this method twice to move an object to a corner: .. code:: python def construct(self): req = Rectangle() req.to_edge(UP) req.to_edge(LEFT) self.add(req) .. image:: ../_static/images/cp4.png This method admits a parameter called ``buff`` (buffer), this parameter indicates a gap between the border and your object, by **default** the value of this buffer is **0.5 units**, but we can reduce this gap to zero using: .. code:: python def construct(self): req = Rectangle() req.to_edge(UP) req.to_edge(LEFT,buff=0) self.add(req) .. image:: ../_static/images/cp5.png Mobject.to_corner() """""""""""""""""""""" This method requires a two-dimensional vector, and places your Mobject in the corner. It is equivalent to using ``Mobject.to_edge()`` twice. It also supports the ``buff`` parameter. .. code:: python def construct(self): req = Rectangle() req.to_corner(UL) self.add(req) .. image:: ../_static/images/cp6.png .. code:: python def construct(self): req = Rectangle() req.to_corner(DR,buff=0) self.add(req) .. image:: ../_static/images/cp7.png Mobject.next_to() """""""""""""""""""""" This method uses the **edge** of a Mobject/point and positions our Mobject in the direction of that edge, the format is as follows: .. code:: python Mobject.next_to(REFERENCE_MOBJECT_OR_POINT, DIRECTION, buff=BUFFER, aligned_edge=EDGE) Here we see some examples: .. code:: python def construct(self): # Reference Mobject: rm = Rectangle() # Mobjects that we want to move: red_dot = Dot(color=RED) blue_dot = Dot(color=BLUE) green_dot = Dot(color=GREEN) t = Text("Some text") # Set positions red_dot.next_to(rm, LEFT) blue_dot.next_to(rm, LEFT, buff=0) green_dot.next_to(rm, DR, buff=0) t.next_to(rm, DOWN, aligned_edge=LEFT) # ----------------- # Delete this parameter and see what # happens, then change LEFT to RIGHT self.add( rm, red_dot, blue_dot, green_dot, t ) .. image:: ../_static/images/cp8.png You can notice that ``.next_to()`` will never move a Mobject to the center of another Mobject, it always takes as a reference the edge that you indicate in the second argument. The parameter ``aligned_edge`` allows you to **align** your Mobject with the edge of the *reference Mobject*. Mobject.align_to() """""""""""""""""""""" This is a somewhat complicated method to understand, but quite useful, its behavior is similar to what you saw with the ``aligned_edge`` parameter of ``.next_to()``. .. image:: ../_static/images/alignto.png .. code:: python def construct(self): c = Circle() c.move_to(RIGHT * 3 + UP * 1.5) r = Rectangle() r.align_to(c,RIGHT) self.add(c,r) .. image:: ../_static/images/cp9.png Also works with corners: .. code:: python def construct(self): r = Rectangle() r.move_to(RIGHT * 3 + UP * 1.5) t = Text("Hello") t.align_to(r,UR) self.add(r,t) .. image:: ../_static/images/cp10.png Width and Height ------------------- Setting """"""""" Obviously, all Mobjects have height and width, additionally, three-dimensional Mobjects also have depth. To be able to modify them it is very simple: .. code:: python def construct(self): c = Circle() r = Rectangle() # replace "width" with "height # and see what happens c.width = 3 r.width = 3 self.add(c,r) .. image:: ../_static/images/wh1.png We can also pass the width from one Mobject to another like this: .. code:: python def construct(self): c = Circle() r = Rectangle() # replace "width" with "height # and see what happens c.width = r.width # or c.scale_to_fit_width(r.width) # c.scale_to_fit_height(r.height) self.add(c,r) .. image:: ../_static/images/wh2.png Another way to define the width or height is using the ``.set`` method: .. code:: python def construct(self): c = Circle() r = Rectangle() # replace "width" with "height # and see what happens c.set(width=3) r.set(width=3) self.add(c,r) Stretch """"""""""" If you don't want the proportions of your Mobject to be kept when changing the width or height then you can use ``.stretch_to_fit_height()`` or ``.stretch_to_fit_width()``: .. code:: python def construct(self): c = Circle() t = Triangle() r = Rectangle() t.stretch_to_fit_height(c.height) r.stretch_to_fit_width(c.width) t.move_to(c.get_center()) # What happend if you remove this line self.add(c,t,r) .. image:: ../_static/images/wh3.png Scale """"""""""" .. code:: python def construct(self): # Original circle c_original = Circle(color=RED) # x2 c_x_2 = Circle(color=WHITE) c_x_2.scale(2) # x3 c_x_3 = Circle(color=BLUE) c_x_3.scale(3) # x 1/3 c_x_1_3 = Circle(color=GREEN) c_x_1_3.scale(1/3) self.add( c_original, c_x_2, c_x_3, c_x_1_3 ) .. image:: ../_static/images/wh3_2.png Apply Matrix """""""""""""" As its name indicates, it applies a **linear transformation** to a Mobject, you can use the following image as a reference. This is a generalization of all the properties previously seen. .. image:: ../_static/images/apply_matrix.svg .. code:: python def construct(self): sq_phantom = Square() sq = Square(color=RED) ANGLE = PI / 6 # Reference point POINT = sq.get_corner(DL) matrix = [ [1,np.tan(ANGLE),0], [0,1,0], [0,0,0] ] sq.apply_matrix(matrix,about_point=POINT) self.add(sq_phantom, sq) .. image:: ../_static/images/wh4.png VMobject attributes --------------------- The previously studied properties work for any Mobject, now we will study the properties that only VMobjects have. The most common VMobjects are: * ``SVGMobject`` * Some Subclasses: ``Text``, ``Tex``, ``MathTex``, ``MarkupText``, etc. * Geometry VMobjects: * ``Line``, ``Arrow``, ``Circle``, ``Rectangle``, etc. Color palette """"""""""""""""" ManimCE's official documentation gives us all the default colors: .. image:: ../_static/images/color_pallete.png You must write them in capital letters. .. note:: The colors of type “C” have an alias equal to the colorname without a letter, e.g. GREEN = GREEN_C You can define the color using the hexadecimal format, with RGB or with HSL: .. code:: python def construct(self): from colour import Color def HSL(hue,saturation=1,lightness=0.5): return Color(hsl=(hue/360,saturation,lightness)) red_dot = Dot(color=RED) .scale(4) .to_edge(UP) blue_e_dot = Dot(color=BLUE_E) .scale(4) .to_edge(DOWN) hex_dot = Dot(color="#FE298D") .scale(4) .to_edge(LEFT) rgb_dot = Dot(color=rgb_to_color([0.2,0.9,0])).scale(4) hsl_color = Dot(color=HSL(45,1,0.5)).scale(4) .to_edge(RIGHT) self.add( red_dot, blue_e_dot, hex_dot, rgb_dot, hsl_color ) .. image:: ../_static/images/color_example.png Stroke width, fill and opacity """"""""""""""""""""""""""""""""""" .. code:: python def construct(self): background_square = Square( fill_opacity=1, fill_color=WHITE, ) background_square.scale(1.5) circle = Circle( # stroke options stroke_width=20, stroke_color=TEAL, stroke_opacity=0.5, # 0 <= stroke_opacity <= 1 # fill options fill_opacity=0.5, # 0 <= fill_opacity <= 1 fill_color=ORANGE ) self.add( background_square, circle ) .. image:: ../_static/images/vm1.png A quick way to set a color is using ``.set_color(SOME_COLOR)``, in case you have already defined the color of the stroke width or the fill, both properties will take the color that you indicated in the ``.set_color()`` method. .. code:: python def construct(self): c = Circle( stroke_color=PINK, stroke_width=30, stroke_opacity=0.4, fill_opacity=0.6, fill_color=ORANGE ) c.set_color(RED) self.add(c) .. image:: ../_static/images/vm2.png Points, start and end """"""""""""""""""""""""""""""""""" As we explained at the beginning, all VMobjects are bézier curves, and therefore have control points, you can visualize them quite easily: .. code:: python def construct(self): c = Circle() # c.points are the control points for p in c.points: d = Dot().move_to(p) self.add(d) self.add(c) .. image:: ../_static/images/po1.png You can even modify the points at runtime: .. code:: python def construct(self): c = Circle() c.points[4] += LEFT for p in c.points: d = Dot().move_to(p) self.add(d) self.add(c) .. image:: ../_static/images/po2.png In general, this type of manipulation is not useful, but with this we can obtain the starting and ending point of our path. In general, this type of manipulation is not useful, but thanks to this we can obtain the starting and ending point of our path. It is very useful when we use it with ``Line`` or similar. .. code:: python def construct(self): arrow = Arrow(LEFT,UR) arrow.shift(LEFT+DOWN) arrow_start = arrow.get_start() # same as arrow.points[0] arrow_end = arrow.get_end() # same as arrow.points[-1] dot_start = Dot(color=RED).move_to(arrow_start) dot_end = Dot(color=BLUE).move_to(arrow_end) self.add(arrow, dot_start, dot_end) .. image:: ../_static/images/po3.png Copies and setters ----------------------------- Sometimes it is convenient to copy an **instance** of a Mobject, to do this we can use the ``.copy()`` method. .. code:: python def construct(self): original_circle = Circle( radius=2, stroke_color=PINK, stroke_width=30, stroke_opacity=0.4, fill_opacity=0.6, fill_color=ORANGE ) original_circle.to_edge(LEFT) copy_circle = original_circle.copy() copy_circle.to_edge(RIGHT) # set_color copy_circle.set_color(RED) # set_stroke copy_circle.set_stroke(color=TEAL,width=50,opacity=1) # set_fill copy_circle.set_fill(color=PURE_BLUE,opacity=1) another_copy_circle = copy_circle.copy() another_copy_circle.move_to(ORIGIN) # set_style another_copy_circle.set_style( stroke_width=30, stroke_color=WHITE, stroke_opacity=0.5, fill_color=PURE_GREEN, fill_opacity=0.3, ) .. image:: ../_static/images/po4.png As you can see, it is possible to change the thickness and padding properties after the instance, either by using ``.set_stroke()``, ``.set_fill()``, or by using ``.set_style()``. .. note:: The ``.copy()`` method works with any Mobject, not just VMobjects. Exercises ------------ 1. Create a grid in which you can see the coordinates of the screen: .. image:: ../_static/images/ex_1.png 2. Draw the Yin-Yang symbol. 3. Draw the VUE.js logo.